1 Commits

Author SHA1 Message Date
e076346765 Start of "production videos", a.k.a. tagged assets overview
Tagged assets are shown in a list per tag. The list is dynamically
loaded with JavaScript.
2018-09-06 15:46:31 +02:00
109 changed files with 5539 additions and 8597 deletions

1
.gitignore vendored
View File

@@ -21,7 +21,6 @@ node_modules/
/docker/2_buildpy/python/ /docker/2_buildpy/python/
/docker/4_run/wheelhouse/ /docker/4_run/wheelhouse/
/docker/4_run/deploy/ /docker/4_run/deploy/
/docker/4_run/staging/
/celerybeat-schedule.bak /celerybeat-schedule.bak
/celerybeat-schedule.dat /celerybeat-schedule.dat
/celerybeat-schedule.dir /celerybeat-schedule.dir

View File

@@ -27,19 +27,14 @@ git clone git://git.blender.org/blender-cloud.git
### Initial setup and configuration ### Initial setup and configuration
Create a virtualenv for the project and install the requirements. Dependencies are managed via Create a virtualenv for the project and install the requirements:
[Poetry](https://poetry.eustace.io/). Install it using `pip install -U --user poetry`.
``` ```
cd blender-cloud cd blender-cloud
pip install --user -U poetry mkvirtualenv blender-cloud -p python3.6
poetry install 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. 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. 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: 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: 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. # pull from master, run unittests, push your changes to master.
git pull git pull
poetry run py.test py.test
git push git push
# Switch to production branch, and investigate the situation. # Switch to production branch, and investigate the situation.
@@ -117,7 +112,7 @@ git prod
git ff master git ff master
# Run tests again # Run tests again
poetry run py.test py.test
# Push the production branch. # Push the production branch.
git push git push
@@ -125,4 +120,13 @@ git push
## Deploying to production server ## 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 import flask
from werkzeug.local import LocalProxy from werkzeug.local import LocalProxy
import pillarsdk
import pillar.auth
from pillar.api.utils import authorization from pillar.api.utils import authorization
from pillar.extension import PillarExtension from pillar.extension import PillarExtension
@@ -43,9 +41,7 @@ class CloudExtension(PillarExtension):
'EXTERNAL_SUBSCRIPTIONS_MANAGEMENT_SERVER': 'https://store.blender.org/api/', 'EXTERNAL_SUBSCRIPTIONS_MANAGEMENT_SERVER': 'https://store.blender.org/api/',
'EXTERNAL_SUBSCRIPTIONS_TIMEOUT_SECS': 10, 'EXTERNAL_SUBSCRIPTIONS_TIMEOUT_SECS': 10,
'BLENDER_ID_WEBHOOK_USER_CHANGED_SECRET': 'oos9wah1Zoa0Yau6ahThohleiChephoi', 'BLENDER_ID_WEBHOOK_USER_CHANGED_SECRET': 'oos9wah1Zoa0Yau6ahThohleiChephoi',
'NODE_TAGS': ['animation', 'modeling', 'rigging', 'sculpting', 'shading', 'texturing', 'lighting', 'NODE_TAGS': ['animation', 'modelling', 'rigging'],
'character-pipeline', 'effects', 'video-editing', 'digital-painting', 'production-design',
'walk-through'],
} }
def eve_settings(self): def eve_settings(self):
@@ -89,54 +85,6 @@ class CloudExtension(PillarExtension):
'current_user_is_subscriber': authorization.user_has_cap('subscriber') '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): def setup_app(self, app):
from . import routes, webhooks, eve_hooks, email from . import routes, webhooks, eve_hooks, email

View File

@@ -9,8 +9,6 @@ import requests
from pillar.cli import manager from pillar.cli import manager
from pillar.api import service from pillar.api import service
from pillar.api.utils import authentication
import cloud.setup
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
@@ -128,12 +126,4 @@ def reconcile_subscribers():
log.info(' skipped : %d', count_skipped) 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) 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 logging
import typing import typing
import bson from flask_login import current_user, login_required
from flask_login import login_required
import flask import flask
import werkzeug.exceptions as wz_exceptions from flask import Blueprint, render_template, redirect, session, url_for, abort, flash
from flask import Blueprint, render_template, redirect, session, url_for, abort, flash, request
from pillarsdk import Node, Project, User, exceptions as sdk_exceptions, Group from pillarsdk import Node, Project, User, exceptions as sdk_exceptions, Group
from pillarsdk.exceptions import ResourceNotFound from pillarsdk.exceptions import ResourceNotFound
import pillar
import pillarsdk
from pillar import current_app from pillar import current_app
from pillar.api.utils import authorization import pillar.api
from pillar.auth import current_user
from pillar.web.users import forms from pillar.web.users import forms
from pillar.web.utils import system_util, get_file, current_user_is_authenticated from pillar.web.utils import system_util, get_file, current_user_is_authenticated
from pillar.web.utils import attach_project_pictures from pillar.web.utils import attach_project_pictures
from pillar.web.settings import blueprint as blueprint_settings from pillar.web.settings import blueprint as blueprint_settings
from pillar.web.nodes.routes import url_for_node 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 render_project
from pillar.web.projects.routes import find_project_or_404 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__) blueprint = Blueprint('cloud', __name__)
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
@@ -51,6 +42,35 @@ def _homepage_context() -> dict:
# Get latest blog posts # Get latest blog posts
api = system_util.pillar_api() 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 # Get latest comments to any node
latest_comments = Node.latest('comments', api=api) latest_comments = Node.latest('comments', api=api)
@@ -69,7 +89,6 @@ def _homepage_context() -> dict:
'name': 1, 'name': 1,
'node_type': 1, 'node_type': 1,
'project': 1, 'project': 1,
'parent': 1,
'properties.url': 1, 'properties.url': 1,
}}, }},
api=api) api=api)
@@ -96,24 +115,23 @@ def _homepage_context() -> dict:
main_project = Project.find(current_app.config['MAIN_PROJECT_ID'], api=api) main_project = Project.find(current_app.config['MAIN_PROJECT_ID'], api=api)
main_project.picture_header = get_file(main_project.picture_header, 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( return dict(
main_project=main_project, main_project=main_project,
latest_posts=latest_posts._items,
latest_comments=latest_comments._items, latest_comments=latest_comments._items,
activity_stream=activity_stream,
random_featured=random_featured) 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') @blueprint.route('/login')
def login(): def login():
from flask import request from flask import request
@@ -151,16 +169,6 @@ def services():
return render_template('services.html') 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') @blueprint.route('/stats')
def stats(): def stats():
return render_template('stats.html') return render_template('stats.html')
@@ -211,35 +219,10 @@ def courses():
def open_projects(): def open_projects():
@current_app.cache.cached(timeout=3600, unless=current_user_is_authenticated) @current_app.cache.cached(timeout=3600, unless=current_user_is_authenticated)
def render_page(): def render_page():
api = system_util.pillar_api() projects = get_projects('film')
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
return render_template( return render_template(
'films.html', 'projects_index_collection.html',
title='films', title='open-projects',
projects=projects._items, projects=projects._items,
api=system_util.pillar_api()) api=system_util.pillar_api())
@@ -278,13 +261,12 @@ def get_random_featured_nodes() -> typing.List[dict]:
'summary': True, 'summary': True,
'picture_square': True}}, 'picture_square': True}},
{'$unwind': {'path': '$nodes_featured'}}, {'$unwind': {'path': '$nodes_featured'}},
{'$sample': {'size': 6}}, {'$sample': {'size': 3}},
{'$lookup': {'from': 'nodes', {'$lookup': {'from': 'nodes',
'localField': 'nodes_featured', 'localField': 'nodes_featured',
'foreignField': '_id', 'foreignField': '_id',
'as': 'node'}}, 'as': 'node'}},
{'$unwind': {'path': '$node'}}, {'$unwind': {'path': '$node'}},
{'$match': {'node._deleted': {'$ne': True}}},
{'$project': {'url': True, {'$project': {'url': True,
'name': True, 'name': True,
'summary': True, 'summary': True,
@@ -294,11 +276,7 @@ def get_random_featured_nodes() -> typing.List[dict]:
'node.permissions': True, 'node.permissions': True,
'node.picture': True, 'node.picture': True,
'node.properties.content_type': True, 'node.properties.content_type': True,
'node.properties.duration_seconds': True, 'node.properties.url': True}},
'node.properties.url': True,
'node._created': True,
}
},
]) ])
featured_node_documents = [] 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. # Turn the project-with-node doc into a node-with-project doc.
node_document = node_info.pop('node') node_document = node_info.pop('node')
node_document['project'] = node_info node_document['project'] = node_info
node_document['_id'] = str(node_document['_id'])
node = Node(node_document) node = Node(node_document)
node.picture = get_file(node.picture, api=api) 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) node.project.picture_square = get_file(node.project.picture_square, api=api)
featured_node_documents.append(node) featured_node_documents.append(node)
@@ -414,9 +393,7 @@ def privacy():
@blueprint.route('/production') @blueprint.route('/production')
def production(): def production():
return render_template( return render_template('production.html')
'production.html',
title='production')
@blueprint.route('/emails/welcome.send') @blueprint.route('/emails/welcome.send')
@@ -444,27 +421,32 @@ def emails_welcome_txt():
return flask.Response(txt, content_type='text/plain; charset=utf-8') return flask.Response(txt, content_type='text/plain; charset=utf-8')
@blueprint.route('/p/<project_url>') @blueprint.route('/nodes/<string(length=24):node_id>/comments')
def project_landing(project_url): def comments_for_node(node_id):
"""Override Pillar project_view endpoint completely. """Overrides the default render_comments_for_node.
The first part of the function is identical to the one in Pillar, but the This is done in order to extend can_post_comments by requiring the
second part (starting with 'Load custom project properties') extends the subscriber capability.
behaviour to support film project landing pages.
""" """
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() 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}, embedded={'header_node': 1},
api=api) api=api)
# Load the header video file, if there is any. # Load the header video file, if there is any.
header_video_file = None header_video_file = None
header_video_node = None header_video_node = None
@@ -474,182 +456,15 @@ def project_landing(project_url):
header_video_file = get_file(project.header_node.properties.file) header_video_file = get_file(project.header_node.properties.file)
header_video_node.picture = get_file(header_video_node.picture) 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({ pages = Node.all({
'where': { 'where': {'project': project._id, 'node_type': 'page'},
'project': project._id,
'node_type': 'page',
'_deleted': {'$ne': True}},
'projection': {'name': 1}}, api=api) 'projection': {'name': 1}}, api=api)
extra_context.update({'pages': pages._items})
template_name = 'projects/landing.html'
return render_project(project, api, return render_project(project, api,
extra_context=extra_context, extra_context={'header_video_file': header_video_file,
template_name=template_name) 'header_video_node': header_video_node,
'pages': pages._items,},
template_name='projects/landing.html')
@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
def setup_app(app): 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)

View File

@@ -101,8 +101,8 @@ def insert_or_fetch_user(wh_payload: dict) -> typing.Optional[dict]:
{'auth.provider': 'blender-id', 'auth.user_id': bid_str}, {'auth.provider': 'blender-id', 'auth.user_id': bid_str},
{'email': {'$in': [wh_payload['old_email'], email]}}, {'email': {'$in': [wh_payload['old_email'], email]}},
]} ]}
db_users = list(users_coll.find(query)) db_users = users_coll.find(query)
user_count = len(db_users) user_count = db_users.count()
if user_count > 1: if user_count > 1:
# Now we have to pay the price for finding users in one query; we # 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. # have to prioritise them and return the one we think is most reliable.
@@ -117,10 +117,6 @@ def insert_or_fetch_user(wh_payload: dict) -> typing.Optional[dict]:
my_log.debug('found user %s', db_user['email']) my_log.debug('found user %s', db_user['email'])
return db_user 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 # Pretend to create the user, so that we can inspect the resulting
# capabilities. This is more future-proof than looking at the list # capabilities. This is more future-proof than looking at the list
# of roles in the webhook payload. # of roles in the webhook payload.
@@ -168,7 +164,6 @@ def user_modified():
'old_email': 'old@example.com', 'old_email': 'old@example.com',
'full_name': 'Harry', 'full_name': 'Harry',
'email': 'new@example'com, 'email': 'new@example'com,
'avatar_changed': True,
'roles': ['role1', 'role2', …]} 'roles': ['role1', 'role2', …]}
""" """
my_log = log.getChild('user_modified') my_log = log.getChild('user_modified')
@@ -185,10 +180,6 @@ def user_modified():
my_log.info('Received update for unknown user %r', payload['old_email']) my_log.info('Received update for unknown user %r', payload['old_email'])
return '', 204 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. # Use direct database updates to change the email and full name.
# Also updates the db_user dict so that local_user below will have # Also updates the db_user dict so that local_user below will have
# the updated information. # the updated information.
@@ -208,11 +199,6 @@ def user_modified():
updates['full_name'] = db_user['username'] updates['full_name'] = db_user['username']
db_user['full_name'] = updates['full_name'] 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: if updates:
users_coll = current_app.db('users') users_coll = current_app.db('users')
update_res = users_coll.update_one({'_id': db_user['_id']}, update_res = users_coll.update_one({'_id': db_user['_id']},
@@ -227,37 +213,3 @@ def user_modified():
subscription.do_update_subscription(local_user, payload) subscription.do_update_subscription(local_user, payload)
return '', 204 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

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

View File

@@ -9,6 +9,7 @@ else
fi fi
ROOT="$(dirname "$(dirname "$($readlink -f "$0")")")" ROOT="$(dirname "$(dirname "$($readlink -f "$0")")")"
PROJECT_NAME="$(basename $ROOT)" PROJECT_NAME="$(basename $ROOT)"
DOCKER_DEPLOYDIR="$ROOT/docker/4_run/deploy"
DOCKER_IMAGE="armadillica/blender_cloud:latest" DOCKER_IMAGE="armadillica/blender_cloud:latest"
REMOTE_SECRET_CONFIG_DIR="/data/config" REMOTE_SECRET_CONFIG_DIR="/data/config"
REMOTE_DOCKER_COMPOSE_DIR="/root/docker" 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,7 +1,5 @@
#!/usr/bin/env bash #!/usr/bin/env bash
DOCKER_IMAGE_NAME=armadillica/pillar_wheelbuilder
set -e set -e
# macOS does not support readlink -f, so we use greadlink instead # macOS does not support readlink -f, so we use greadlink instead
@@ -23,40 +21,25 @@ fi
echo "Wheelhouse is $WHEELHOUSE" echo "Wheelhouse is $WHEELHOUSE"
mkdir -p "$WHEELHOUSE" mkdir -p "$WHEELHOUSE"
rm -f "$WHEELHOUSE"/*
docker build -t $DOCKER_IMAGE_NAME:latest . docker build -t pillar_wheelbuilder .
GID=$(id -g) GID=$(id -g)
docker run --rm -i \ docker run --rm -i \
-v "$WHEELHOUSE:/data/wheelhouse" \ -v "$WHEELHOUSE:/data/wheelhouse" \
-v "$TOPDEVDIR:/data/topdev" \ -v "$TOPDEVDIR:/data/topdev" \
$DOCKER_IMAGE_NAME <<EOT pillar_wheelbuilder <<EOT
set -e 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. # Build wheels for all dependencies.
cd /data/topdev/blender-cloud 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 # Install the dependencies so that we can get a full freeze.
pip3 install --no-index --find-links=/data/wheelhouse -r requirements.txt
# Apparently pip doesn't like projects without setup.py, so it think we have 'pillar-svnman' as pip3 freeze | grep -v '^-[ef] ' > /data/wheelhouse/requirements.txt
# 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
EOT EOT
# Remove our own projects, they shouldn't be installed as wheel (for now). # Remove our own projects, they shouldn't be installed as wheel (for now).
rm -f $WHEELHOUSE/{attract,flamenco,pillar,pillarsdk}*.whl rm -f $WHEELHOUSE/{attract,flamenco,pillar,pillarsdk}*.whl
echo "Build of $DOCKER_IMAGE_NAME:latest is done."

View File

@@ -38,9 +38,8 @@ ENV USE_X_SENDFILE True
EXPOSE 80 EXPOSE 80
EXPOSE 5000 EXPOSE 5000
ADD apache/remoteip.conf /etc/apache2/mods-available/
ADD apache/wsgi-py36.* /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/apache2.conf /etc/apache2/apache2.conf
ADD apache/000-default.conf /etc/apache2/sites-available/000-default.conf ADD apache/000-default.conf /etc/apache2/sites-available/000-default.conf
@@ -58,7 +57,7 @@ ENTRYPOINT /docker-entrypoint.sh
# Add the most-changing files as last step for faster rebuilds. # Add the most-changing files as last step for faster rebuilds.
ADD config_local.py /data/git/blender-cloud/ ADD config_local.py /data/git/blender-cloud/
ADD staging /data/git ADD deploy /data/git
RUN python3 -c "import re, secrets; \ RUN python3 -c "import re, secrets; \
f = open('/data/git/blender-cloud/config_local.py', 'a'); \ f = open('/data/git/blender-cloud/config_local.py', 'a'); \
h = re.sub(r'[_.~-]', '', secrets.token_urlsafe())[:8]; \ h = re.sub(r'[_.~-]', '', secrets.token_urlsafe())[:8]; \

View File

@@ -49,8 +49,6 @@
RewriteRule "^/training/?$" "/courses" [R=301,L] RewriteRule "^/training/?$" "/courses" [R=301,L]
RewriteRule "^/spring/?$" "/p/spring" [R=301,L] RewriteRule "^/spring/?$" "/p/spring" [R=301,L]
RewriteRule "^/hero/?$" "/p/hero" [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 # Waking the forest was moved from the art gallery to its own workshop
RewriteRule "^/p/gallery/58cfec4f88ac8f1440aeb309/?$" "/p/waking-the-forest" [R=301,L] RewriteRule "^/p/gallery/58cfec4f88ac8f1440aeb309/?$" "/p/waking-the-forest" [R=301,L]
</VirtualHost> </VirtualHost>

View File

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

View File

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

View File

@@ -106,6 +106,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_REPO_URL = 'https://svn.blender.cloud/repo/'
SVNMAN_API_URL = 'https://svn.blender.cloud/api/' SVNMAN_API_URL = 'https://svn.blender.cloud/api/'

View File

@@ -1,7 +1,7 @@
version: '3.4' version: '3.4'
services: services:
mongo: mongo:
image: mongo:3.4 image: mongo:3.4.2
container_name: mongo container_name: mongo
restart: always restart: always
volumes: volumes:
@@ -15,12 +15,8 @@ services:
max-size: "200k" max-size: "200k"
max-file: "20" max-file: "20"
# Databases in use:
# 0: Flask Cache
# 1: Celery (backend)
# 2: Celery (broker)
redis: redis:
image: redis:5.0 image: redis:3.2.8
container_name: redis container_name: redis
restart: always restart: always
ports: ports:
@@ -31,8 +27,19 @@ services:
max-size: "200k" max-size: "200k"
max-file: "20" 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: elastic:
# This image is defined in blender-cloud/docker/elastic
image: armadillica/elasticsearch:6.1.1 image: armadillica/elasticsearch:6.1.1
container_name: elastic container_name: elastic
restart: always restart: always
@@ -63,7 +70,6 @@ services:
max-file: "20" max-file: "20"
kibana: kibana:
# This image is defined in blender-cloud/docker/elastic
image: armadillica/kibana:6.1.1 image: armadillica/kibana:6.1.1
container_name: kibana container_name: kibana
restart: always restart: always
@@ -103,6 +109,7 @@ services:
depends_on: depends_on:
- mongo - mongo
- redis - redis
- rabbit
celery_worker: celery_worker:
image: armadillica/blender_cloud:latest image: armadillica/blender_cloud:latest
@@ -119,6 +126,7 @@ services:
depends_on: depends_on:
- mongo - mongo
- redis - redis
- rabbit
logging: logging:
driver: "json-file" driver: "json-file"
options: options:
@@ -140,6 +148,7 @@ services:
depends_on: depends_on:
- mongo - mongo
- redis - redis
- rabbit
- celery_worker - celery_worker
logging: logging:
driver: "json-file" driver: "json-file"
@@ -160,8 +169,7 @@ services:
- /data/letsencrypt:/data/letsencrypt - /data/letsencrypt:/data/letsencrypt
haproxy: haproxy:
# This image is defined in blender-cloud/docker/haproxy image: dockercloud/haproxy:1.5.3
image: armadillica/haproxy:1.6.7
container_name: haproxy container_name: haproxy
restart: always restart: always
ports: 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" # Treat "gulp watch" as "gulp && gulp watch"
$GULP $GULP
elif [ "$1" == "all" ]; then elif [ "$1" == "all" ]; then
pushd .
# This is useful when building the Blender Cloud project for the first time. # 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) # Run ./gulp in all depending projects (pillar, attract, flamenco, pillar-svnman)
declare -a repos=("pillar" "attract" "flamenco" "pillar-svnman") declare -a repos=("pillar" "attract" "flamenco" "pillar-svnman")
for r in "${repos[@]}" for r in "${repos[@]}"
@@ -24,9 +25,6 @@ elif [ "$1" == "all" ]; then
cd ../$r cd ../$r
./gulp ./gulp
done done
popd
# Run "gulp" once inside the repo
$GULP
exit 1 exit 1
fi fi

View File

@@ -1,19 +1,20 @@
let argv = require('minimist')(process.argv.slice(2)); var argv = require('minimist')(process.argv.slice(2));
let autoprefixer = require('gulp-autoprefixer'); var autoprefixer = require('gulp-autoprefixer');
let cache = require('gulp-cached'); var cache = require('gulp-cached');
let chmod = require('gulp-chmod'); var chmod = require('gulp-chmod');
let concat = require('gulp-concat'); var concat = require('gulp-concat');
let git = require('gulp-git'); var git = require('gulp-git');
let gulp = require('gulp'); var gulp = require('gulp');
let gulpif = require('gulp-if'); var gulpif = require('gulp-if');
let pug = require('gulp-pug'); var pug = require('gulp-pug');
let plumber = require('gulp-plumber'); var livereload = require('gulp-livereload');
let rename = require('gulp-rename'); var plumber = require('gulp-plumber');
let sass = require('gulp-sass'); var rename = require('gulp-rename');
let sourcemaps = require('gulp-sourcemaps'); var sass = require('gulp-sass');
let uglify = require('gulp-uglify-es').default; var sourcemaps = require('gulp-sourcemaps');
var uglify = require('gulp-uglify-es').default;
let enabled = { var enabled = {
uglify: argv.production, uglify: argv.production,
maps: !argv.production, maps: !argv.production,
failCheck: !argv.production, failCheck: !argv.production,
@@ -23,19 +24,21 @@ let enabled = {
chmod: argv.production, chmod: argv.production,
}; };
let destination = { var destination = {
css: 'cloud/static/assets/css', css: 'cloud/static/assets/css',
pug: 'cloud/templates', pug: 'cloud/templates',
js: 'cloud/static/assets/js', js: 'cloud/static/assets/js',
} }
let source = { var source = {
pillar: '../pillar/' pillar: '../pillar/',
bootstrap: 'node_modules/bootstrap/',
popper: 'node_modules/popper.js/'
} }
/* CSS */ /* CSS */
gulp.task('styles', function(done) { gulp.task('styles', function() {
gulp.src('src/styles/**/*.sass') gulp.src('src/styles/**/*.sass')
.pipe(gulpif(enabled.failCheck, plumber())) .pipe(gulpif(enabled.failCheck, plumber()))
.pipe(gulpif(enabled.maps, sourcemaps.init())) .pipe(gulpif(enabled.maps, sourcemaps.init()))
@@ -44,34 +47,32 @@ gulp.task('styles', function(done) {
)) ))
.pipe(autoprefixer("last 3 versions")) .pipe(autoprefixer("last 3 versions"))
.pipe(gulpif(enabled.maps, sourcemaps.write("."))) .pipe(gulpif(enabled.maps, sourcemaps.write(".")))
.pipe(gulp.dest(destination.css)); .pipe(gulp.dest(destination.css))
done(); .pipe(gulpif(argv.livereload, livereload()));
}); });
/* Templates - Pug */ /* Templates - Pug */
gulp.task('templates', function(done) { gulp.task('templates', function() {
gulp.src('src/templates/**/*.pug') gulp.src('src/templates/**/*.pug')
.pipe(gulpif(enabled.failCheck, plumber())) .pipe(gulpif(enabled.failCheck, plumber()))
.pipe(gulpif(enabled.cachify, cache('templating'))) .pipe(gulpif(enabled.cachify, cache('templating')))
.pipe(pug({ .pipe(pug({
pretty: enabled.prettyPug pretty: enabled.prettyPug
})) }))
.pipe(gulp.dest(destination.pug)); .pipe(gulp.dest(destination.pug))
.pipe(gulpif(argv.livereload, livereload()));
// TODO(venomgfx): please check why 'gulp watch' doesn't pick up on .txt changes. // TODO(venomgfx): please check why 'gulp watch' doesn't pick up on .txt changes.
gulp.src('src/templates/**/*.txt') gulp.src('src/templates/**/*.txt')
.pipe(gulpif(enabled.failCheck, plumber())) .pipe(gulpif(enabled.failCheck, plumber()))
.pipe(gulpif(enabled.cachify, cache('templating'))) .pipe(gulpif(enabled.cachify, cache('templating')))
.pipe(gulp.dest(destination.pug)); .pipe(gulp.dest(destination.pug))
done(); .pipe(gulpif(argv.livereload, livereload()));
}); });
/* Tutti gets built by Pillar. See gulpfile.js in pillar.*/
/* Individual Uglified Scripts */ /* Individual Uglified Scripts */
gulp.task('scripts', function(done) { gulp.task('scripts', function() {
gulp.src('src/scripts/*.js') gulp.src('src/scripts/*.js')
.pipe(gulpif(enabled.failCheck, plumber())) .pipe(gulpif(enabled.failCheck, plumber()))
.pipe(gulpif(enabled.cachify, cache('scripting'))) .pipe(gulpif(enabled.cachify, cache('scripting')))
@@ -79,39 +80,68 @@ gulp.task('scripts', function(done) {
.pipe(gulpif(enabled.uglify, uglify())) .pipe(gulpif(enabled.uglify, uglify()))
.pipe(rename({suffix: '.min'})) .pipe(rename({suffix: '.min'}))
.pipe(gulpif(enabled.maps, sourcemaps.write("."))) .pipe(gulpif(enabled.maps, sourcemaps.write(".")))
.pipe(gulpif(enabled.chmod, chmod(0o644))) .pipe(gulpif(enabled.chmod, chmod(644)))
.pipe(gulp.dest(destination.js)); .pipe(gulp.dest(destination.js))
done(); .pipe(gulpif(argv.livereload, livereload()));
});
/* 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_concat_tutti', function() {
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(gulpif(enabled.chmod, chmod(644)))
.pipe(gulp.dest(destination.js))
.pipe(gulpif(argv.livereload, livereload()));
});
// Combine all needed Bootstrap JavaScript into a single file.
gulp.task('scripts_concat_bootstrap', function() {
toUglify = [
source.popper + 'dist/umd/popper.min.js',
source.bootstrap + 'js/dist/index.js',
source.bootstrap + 'js/dist/util.js',
source.bootstrap + 'js/dist/tooltip.js',
source.bootstrap + 'js/dist/dropdown.js',
];
gulp.src(toUglify)
.pipe(gulpif(enabled.failCheck, plumber()))
.pipe(gulpif(enabled.maps, sourcemaps.init()))
.pipe(concat("bootstrap.min.js"))
.pipe(gulpif(enabled.uglify, uglify()))
.pipe(gulpif(enabled.maps, sourcemaps.write(".")))
.pipe(gulpif(enabled.chmod, chmod(644)))
.pipe(gulp.dest(destination.js))
.pipe(gulpif(argv.livereload, livereload()));
}); });
// While developing, run 'gulp watch' // While developing, run 'gulp watch'
gulp.task('watch',function(done) { gulp.task('watch',function() {
let watchStyles = [ // Only listen for live reloads if ran with --livereload
'src/styles/**/*.sass', if (argv.livereload){
source.pillar + 'src/styles/**/*.sass', livereload.listen();
]; }
let watchScripts = [ gulp.watch('src/styles/**/*.sass',['styles']);
'src/scripts/**/*.js', gulp.watch(source.pillar + 'src/styles/**/*.sass',['styles']);
source.pillar + 'src/scripts/**/*.js',
];
let watchTemplates = [ gulp.watch('src/templates/**/*.pug',['templates']);
'src/templates/**/*.pug', gulp.watch('src/scripts/*.js',['scripts']);
source.pillar + 'src/templates/**/*.pug', gulp.watch('src/scripts/tutti/**/*.js',['scripts_concat_tutti']);
];
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. // Erases all generated files in output directories.
gulp.task('cleanup', function(done) { gulp.task('cleanup', function() {
let paths = []; var paths = [];
for (attr in destination) { for (attr in destination) {
paths.push(destination[attr]); paths.push(destination[attr]);
} }
@@ -119,12 +149,12 @@ gulp.task('cleanup', function(done) {
git.clean({ args: '-f -X ' + paths.join(' ') }, function (err) { git.clean({ args: '-f -X ' + paths.join(' ') }, function (err) {
if(err) throw err; if(err) throw err;
}); });
done();
}); });
// Run 'gulp' to build everything at once // Run 'gulp' to build everything at once
let tasks = []; var tasks = [];
if (enabled.cleanup) tasks.push('cleanup'); if (enabled.cleanup) tasks.push('cleanup');
gulp.task('default', gulp.parallel(tasks.concat(['styles', 'templates', 'scripts']))); gulp.task('default', tasks.concat(['styles', 'templates', 'scripts', 'scripts_concat_tutti', 'scripts_concat_bootstrap']));

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

6584
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -7,17 +7,18 @@
"url": "git://git.blender.org/blender-cloud.git" "url": "git://git.blender.org/blender-cloud.git"
}, },
"devDependencies": { "devDependencies": {
"gulp": "~4.0", "gulp": "~3.9.1",
"gulp-autoprefixer": "~6.0.0", "gulp-autoprefixer": "~6.0.0",
"gulp-cached": "~1.1.1", "gulp-cached": "~1.1.1",
"gulp-chmod": "~2.0.0", "gulp-chmod": "~2.0.0",
"gulp-concat": "~2.6.1", "gulp-concat": "~2.6.1",
"gulp-if": "^2.0.2", "gulp-if": "^2.0.2",
"gulp-git": "~2.8.0", "gulp-git": "~2.8.0",
"gulp-livereload": "~4.0.0",
"gulp-plumber": "~1.2.0", "gulp-plumber": "~1.2.0",
"gulp-pug": "~4.0.1", "gulp-pug": "~4.0.1",
"gulp-rename": "~1.4.0", "gulp-rename": "~1.4.0",
"gulp-sass": "~4.1.0", "gulp-sass": "~4.0.1",
"gulp-sourcemaps": "~2.6.4", "gulp-sourcemaps": "~2.6.4",
"gulp-uglify-es": "^1.0.4", "gulp-uglify-es": "^1.0.4",
"minimist": "^1.2.0" "minimist": "^1.2.0"
@@ -25,8 +26,6 @@
"dependencies": { "dependencies": {
"bootstrap": "^4.1.3", "bootstrap": "^4.1.3",
"jquery": "^3.3.1", "jquery": "^3.3.1",
"natives": "^1.1.6", "popper.js": "^1.14.4"
"popper.js": "^1.14.4",
"video.js": "^7.2.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"

12
requirements-dev.txt Normal file
View File

@@ -0,0 +1,12 @@
-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
-e .

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,
)

View File

@@ -2,27 +2,107 @@
* Support for fetching & rendering assets by tags. * Support for fetching & rendering assets by tags.
*/ */
(function($) { (function($) {
$.fn.loadTaggedAssets = function(load_initial_count, load_next_count, has_subscription) { /* How many nodes to load initially, and when clicked on the 'Load Next' link. */
mark_if_public = !has_subscription; const LOAD_INITIAL_COUNT = 5;
this.each(function(index, each) { const LOAD_NEXT_COUNT = 3;
let $card_deck_element = $(each)
$card_deck_element.trigger('pillar:workStart'); /* Renders a node as a <li> element, returns a jQuery object. */
$.get('/api/nodes/tagged/' + $card_deck_element.data('assetTag')) function renderAsset(node) {
let li = $('<li>').addClass('tagged-asset');
let link = $('<a>')
.attr('href', '/nodes/' + node._id + '/redir')
.appendTo(li);
function warnNoPicture() {
li.addClass('warning');
link.text('no picture for node ' + node._id);
}
if (!node.picture) {
warnNoPicture();
return li;
}
// TODO: show 'loading' thingy
$.get('/api/files/' + node.picture)
.fail(function(error) { .fail(function(error) {
let msg = xhrErrorResponseMessage(error); let msg = xhrErrorResponseMessage(error);
$card_deck_element li.addClass('error').text(msg);
.append( })
$('<p>').addClass('bg-danger').text(msg) .done(function(resp) {
); // Render the picture if it has the proper size.
var show_variation = null;
if (typeof resp.variations != 'undefined') {
for (variation of resp.variations) {
if (variation.size != 'm') continue;
show_variation = variation;
break;
}
}
if (show_variation == null) {
warnNoPicture();
return;
}
let img = $('<img>')
.attr('alt', node.name)
.attr('src', variation.link)
.attr('width', variation.width)
.attr('height', variation.height);
link.append(img);
});
return li;
}
function loadNext(ul_element) {
let $ul = $(ul_element);
let tagged_assets = ul_element.tagged_assets; // Stored here by loadTaggedAssets().
let already_loaded = $ul.find('li.tagged-asset').length;
let load_next = $ul.find('li.load-next');
let nodes_to_load = tagged_assets.slice(already_loaded, already_loaded + LOAD_NEXT_COUNT);
for (node of nodes_to_load) {
let li = renderAsset(node);
load_next.before(li);
}
if (already_loaded + LOAD_NEXT_COUNT >= tagged_assets.length)
load_next.remove();
}
$.fn.loadTaggedAssets = function(api_base_url) {
this.each(function(index, ul_element) {
// TODO(Sybren): show a 'loading' animation.
$.get('/api/nodes/tagged/' + ul_element.dataset.assetTag)
.fail(function(error) {
let msg = xhrErrorResponseMessage(error);
$('<li>').addClass('error').text(msg).appendTo(ul_element);
}) })
.done(function(resp) { .done(function(resp) {
// 'resp' is a list of node documents. // 'resp' is a list of node documents.
$card_deck_element.append( // Store the response on the DOM <ul>-element so that we can later render more.
pillar.templates.Nodes.createListOf$nodeItems(resp, load_initial_count, load_next_count) ul_element.tagged_assets = resp;
);
}) // Here render the first N.
.always(function() { for (node of resp.slice(0, LOAD_INITIAL_COUNT)) {
$card_deck_element.trigger('pillar:workStop'); let li = renderAsset(node);
li.appendTo(ul_element);
}
// Don't bother with a 'load next' link if there is no more.
if (resp.length <= LOAD_INITIAL_COUNT) return;
// Construct the 'load next' link.
let load_next = $('<li>').addClass('load-next');
let link = $('<a>')
.attr('href', 'javascript:void(0);')
.click(function() { loadNext(ul_element); return false; })
.text('Load next')
.appendTo(load_next);
load_next.appendTo(ul_element);
}); });
}); });
}; };

View File

@@ -1,60 +1,502 @@
.random-featured
// Hide irrelevant info from cards.
li
&.item-type,
&.item-date
@extend .d-none
// 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%
.homepage .title-underline
.timeline padding-bottom: 5px
.card-text, position: relative
.card-title margin-bottom: 20px
margin-bottom: 0 !important
padding: 0 !important
// Hide project name, it's already in the timeline. &:before
background-color: $primary
content: ' '
display: block
height: 2px
top: 125%
position: absolute
width: 50px
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
li.nav-tabs__list-tab
float: left
border: none
border-bottom: 3px solid transparent
color: $color-text-dark-primary
user-select: none
&:hover
border-color: rgba($color-secondary, .3)
cursor: pointer
color: $color-text-dark
a a
color: $color-text-dark
a
display: block
text-decoration: none
padding: 10px 15px 5px
color: $color-text-dark-primary
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
.dashboard-container
word-break: break-word
section.stream
ul.activity-stream__list
$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-details
padding: 0
.title
color: $color-text-dark
padding: 7px 10px 2px 10px
font-size: 1em
margin: 0
ul.meta
+list-meta
font-size: .9em
padding: 0 10px 7px 10px
li
&.where-parent:before
content: '\e83a'
font-family: 'pillar-font'
&.what:before
display: none display: none
// On blog posts, center text and title. &.post
.h1.text-uppercase .activity-stream__list-thumbnail
+media-md 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 text-align: center
+media-xl span
text-align: left color: $color-primary
&:hover
text-decoration: underline
cursor: pointer
.node-details-description &.with-picture
+media-md min-height: $activity-stream-thumbnail-size
margin-left: auto
margin-right: auto .activity-stream__list-thumbnail
+media-xl 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%
padding: 10px 0
+media-xs
margin-left: 0 margin-left: 0
margin-right: auto
.featured-projects .ribbon
+ribbon
right: -47px
top: 5px
font-size: 12px
span
padding: 1px 50px
.title
padding: 0 10px
color: $color-text-dark
span
@include badge(hsl(hue($color-success), 60%, 45%), 3px)
font-size: .7em
padding: 1px 5px
margin-right: 5px
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.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 +media-sm
padding-left: $spacer height: 500px
padding-right: $spacer +media-md
height: 520px
+media-lg
height: 580px
.featured-project-card .text
+media-xl padding: 15px
.card-thumbnail
height: 100%
border-radius: $border-radius
.card-body .title
padding-left: 15px !important padding-bottom: 10px
padding-top: 0 !important
.homepage +media-xs
.title-underline font-size: 1.4em
padding-bottom: 2px
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
body.homepage
.blog
// Custom tweak to Bootstrap grid for the only case when
// the post is inside a column (it's usually centered in the page).
.col-md-9
flex: 1
max-width: 100%
.jumbotron
padding-top: 6em
padding-bottom: 6em
*
font-size: $h1-font-size
.lead
font-size: $font-size-base

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

@@ -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

@@ -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

@@ -312,7 +312,7 @@ section.pricing
transform: scale(1) transform: scale(1)
a.sign-up-now a.sign-up-now
+button($color-primary, $btn-border-radius, true) +button($color-primary, 3px, true)
h3 h3
font-size: 1.8em font-size: 1.8em
@@ -361,7 +361,7 @@ section.pricing
transform: translateX(-50%) transform: translateX(-50%)
font-size: 1.2em font-size: 1.2em
+button($color-primary, $btn-border-radius) +button($color-primary, 3px)
padding: 5px 25px padding: 5px 25px
white-space: nowrap white-space: nowrap
text-align: center text-align: center

View File

@@ -1,53 +1,51 @@
// Bootstrap variables and utilities. // Bootstrap variables and utilities.
@import "../../../pillar/node_modules/bootstrap/scss/functions" @import "../../node_modules/bootstrap/scss/functions"
@import "../../../pillar/node_modules/bootstrap/scss/variables" @import "../../node_modules/bootstrap/scss/variables"
@import "../../../pillar/node_modules/bootstrap/scss/mixins" @import "../../node_modules/bootstrap/scss/mixins"
// Pillar variables and utilities. // Pillar variables and utilities.
@import "../../../pillar/src/styles/config" @import "../../../pillar/src/styles/config"
@import "../../../pillar/src/styles/utils" @import "../../../pillar/src/styles/utils"
$pillar-font-path: "../../../../static/pillar/assets/font"
@import "../../../pillar/src/styles/font-pillar"
// Bootstrap components. // Bootstrap components.
@import "../../../pillar/node_modules/bootstrap/scss/root" @import "../../node_modules/bootstrap/scss/root"
@import "../../../pillar/node_modules/bootstrap/scss/reboot" @import "../../node_modules/bootstrap/scss/reboot"
@import "../../../pillar/node_modules/bootstrap/scss/type" @import "../../node_modules/bootstrap/scss/type"
@import "../../../pillar/node_modules/bootstrap/scss/images" @import "../../node_modules/bootstrap/scss/images"
@import "../../../pillar/node_modules/bootstrap/scss/code" @import "../../node_modules/bootstrap/scss/code"
@import "../../../pillar/node_modules/bootstrap/scss/grid" @import "../../node_modules/bootstrap/scss/grid"
@import "../../../pillar/node_modules/bootstrap/scss/tables" @import "../../node_modules/bootstrap/scss/tables"
@import "../../../pillar/node_modules/bootstrap/scss/forms" @import "../../node_modules/bootstrap/scss/forms"
@import "../../../pillar/node_modules/bootstrap/scss/buttons" @import "../../node_modules/bootstrap/scss/buttons"
@import "../../../pillar/node_modules/bootstrap/scss/transitions" @import "../../node_modules/bootstrap/scss/transitions"
@import "../../../pillar/node_modules/bootstrap/scss/dropdown" @import "../../node_modules/bootstrap/scss/dropdown"
@import "../../../pillar/node_modules/bootstrap/scss/button-group" @import "../../node_modules/bootstrap/scss/button-group"
@import "../../../pillar/node_modules/bootstrap/scss/input-group" @import "../../node_modules/bootstrap/scss/input-group"
@import "../../../pillar/node_modules/bootstrap/scss/custom-forms" @import "../../node_modules/bootstrap/scss/custom-forms"
@import "../../../pillar/node_modules/bootstrap/scss/nav" @import "../../node_modules/bootstrap/scss/nav"
@import "../../../pillar/node_modules/bootstrap/scss/navbar" @import "../../node_modules/bootstrap/scss/navbar"
@import "../../../pillar/node_modules/bootstrap/scss/card" @import "../../node_modules/bootstrap/scss/card"
@import "../../../pillar/node_modules/bootstrap/scss/breadcrumb" @import "../../node_modules/bootstrap/scss/breadcrumb"
@import "../../../pillar/node_modules/bootstrap/scss/pagination" @import "../../node_modules/bootstrap/scss/pagination"
@import "../../../pillar/node_modules/bootstrap/scss/badge" @import "../../node_modules/bootstrap/scss/badge"
@import "../../../pillar/node_modules/bootstrap/scss/jumbotron" @import "../../node_modules/bootstrap/scss/jumbotron"
@import "../../../pillar/node_modules/bootstrap/scss/alert" @import "../../node_modules/bootstrap/scss/alert"
@import "../../../pillar/node_modules/bootstrap/scss/progress" @import "../../node_modules/bootstrap/scss/progress"
@import "../../../pillar/node_modules/bootstrap/scss/media" @import "../../node_modules/bootstrap/scss/media"
@import "../../../pillar/node_modules/bootstrap/scss/list-group" @import "../../node_modules/bootstrap/scss/list-group"
@import "../../../pillar/node_modules/bootstrap/scss/close" @import "../../node_modules/bootstrap/scss/close"
@import "../../../pillar/node_modules/bootstrap/scss/modal" @import "../../node_modules/bootstrap/scss/modal"
@import "../../../pillar/node_modules/bootstrap/scss/tooltip" @import "../../node_modules/bootstrap/scss/tooltip"
@import "../../../pillar/node_modules/bootstrap/scss/popover" @import "../../node_modules/bootstrap/scss/popover"
@import "../../../pillar/node_modules/bootstrap/scss/carousel" @import "../../node_modules/bootstrap/scss/carousel"
@import "../../../pillar/node_modules/bootstrap/scss/utilities" @import "../../node_modules/bootstrap/scss/utilities"
@import "../../../pillar/node_modules/bootstrap/scss/print" @import "../../node_modules/bootstrap/scss/print"
// Pillar components. // Pillar components.
@@ -74,8 +72,6 @@ $pillar-font-path: "../../../../static/pillar/assets/font"
@import "../../../pillar/src/styles/components/checkbox" @import "../../../pillar/src/styles/components/checkbox"
@import "../../../pillar/src/styles/components/overlay" @import "../../../pillar/src/styles/components/overlay"
@import "../../../pillar/src/styles/components/card" @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/comments"
@import "../../../pillar/src/styles/notifications" @import "../../../pillar/src/styles/notifications"
@@ -86,6 +82,10 @@ $pillar-font-path: "../../../../static/pillar/assets/font"
@import "../../../pillar/src/styles/_project-dashboard" @import "../../../pillar/src/styles/_project-dashboard"
@import "../../../pillar/src/styles/_user" @import "../../../pillar/src/styles/_user"
@import _welcome
@import _homepage
@import _services
@import _about
@import "../../../pillar/src/styles/_search" @import "../../../pillar/src/styles/_search"
@import "../../../pillar/src/styles/_organizations" @import "../../../pillar/src/styles/_organizations"
@@ -97,12 +97,3 @@ $pillar-font-path: "../../../../static/pillar/assets/font"
@import "../../../pillar/src/styles/plugins/_js_select2" @import "../../../pillar/src/styles/plugins/_js_select2"
/* CSS for pillar-font comes from fontello.com using static/assets/font/config.json */ /* 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,384 @@
// Bootstrap variables and utilities.
@import "../../node_modules/bootstrap/scss/functions"
@import "../../node_modules/bootstrap/scss/variables"
@import "../../node_modules/bootstrap/scss/mixins"
// Pillar variables and utilities.
@import "../../../pillar/src/styles/config"
@import "../../../pillar/src/styles/utils"
// Bootstrap components.
@import "../../node_modules/bootstrap/scss/root"
@import "../../node_modules/bootstrap/scss/reboot"
@import "../../node_modules/bootstrap/scss/type"
@import "../../node_modules/bootstrap/scss/images"
@import "../../node_modules/bootstrap/scss/code"
@import "../../node_modules/bootstrap/scss/grid"
@import "../../node_modules/bootstrap/scss/tables"
@import "../../node_modules/bootstrap/scss/forms"
@import "../../node_modules/bootstrap/scss/buttons"
@import "../../node_modules/bootstrap/scss/transitions"
@import "../../node_modules/bootstrap/scss/dropdown"
@import "../../node_modules/bootstrap/scss/button-group"
@import "../../node_modules/bootstrap/scss/input-group"
@import "../../node_modules/bootstrap/scss/custom-forms"
@import "../../node_modules/bootstrap/scss/nav"
@import "../../node_modules/bootstrap/scss/navbar"
@import "../../node_modules/bootstrap/scss/card"
@import "../../node_modules/bootstrap/scss/breadcrumb"
@import "../../node_modules/bootstrap/scss/pagination"
@import "../../node_modules/bootstrap/scss/badge"
@import "../../node_modules/bootstrap/scss/jumbotron"
@import "../../node_modules/bootstrap/scss/alert"
@import "../../node_modules/bootstrap/scss/progress"
@import "../../node_modules/bootstrap/scss/media"
@import "../../node_modules/bootstrap/scss/list-group"
@import "../../node_modules/bootstrap/scss/close"
@import "../../node_modules/bootstrap/scss/modal"
@import "../../node_modules/bootstrap/scss/tooltip"
@import "../../node_modules/bootstrap/scss/popover"
@import "../../node_modules/bootstrap/scss/carousel"
@import "../../node_modules/bootstrap/scss/utilities"
@import "../../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/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/notifications"
@import "../../../pillar/src/styles/_search"
$node-latest-thumbnail-size: 160px
$node-latest-gallery-thumbnail-size: 200px
nav.navbar
.navbar-header
+media-xs
width: 100%
.navbar-toggle
border: none
color: $color-text
position: absolute
right: 10px
.navbar-nav
+media-xs
padding: 10px
.search-input
display: none
.node-details-container
max-width: 620px
font-size: 1.3em
line-height: 1.5em
margin: 0 auto 40px auto
padding-bottom: 40px
+media-xs
padding-left: 10px
padding-right: 10px
p
margin-bottom: 1.3em
header
display: flex
flex-direction: column /* stack flex items vertically */
position: relative
img.header
width: 100%
flex-direction: column /* stack flex items vertically */
position: relative
a.page-card-cta
position: absolute
left: 76%
top: 50%
transform: translate(-50%, -50%)
color: white
font-weight: bold
background: #ff4970
border-radius: 3px
border: none
box-shadow: 1px 1px 0 rgba(black, .2)
padding: 7px 20px
text-decoration: none
text-shadow: none
&:hover
background: lighten(#ff4970, 5%)
.landing
h2
text-align: center
margin-bottom: 40px
section
max-width: 1024px
padding-top: 20px
border-top: thin solid $color-background
margin: 0 auto
.navbar-secondary
max-width: 620px
margin: 0 auto
.navbar-collapse
padding-left: 0
li
a
padding-left: 20px
padding-right: 20px
color: $color-text
&:hover
&.active
background: none
color: black
box-shadow: 0px 2px 0 rgba(red, .8)
.node-extra
display: flex
flex-direction: column
//padding: 0 20px
width: 100%
.node-updates
flex: 1
font-size: 1.1em
ul
padding: 0
margin: 0 0 15px 0
display: flex
flex-direction: row
flex-wrap: wrap
li
display: flex
flex-direction: column
list-style: none
padding: 5px
cursor: pointer
width: 33.3333%
+media-xs
width: 100%
&.texture, &.group_texture
width: 25%
&:hover
img
opacity: .9
a.title
//color: $color-primary
text-decoration: underline
&.post
.info .title
//color: $node-type-post
font-size: 1.1em
a.image
border: none
//border-color: $node-type-post
background-color: hsl(hue($node-type-post), 20%, 55%)
&.asset.image a.image
border-color: $node-type-asset_image
background-color: hsl(hue($node-type-asset_image), 20%, 55%)
&.asset.file a.image
border-color: $node-type-asset_file
background-color: hsl(hue($node-type-asset_file), 20%, 55%)
&.asset.video a.image
border-color: $node-type-asset_video
background-color: hsl(hue($node-type-asset_video), 20%, 55%)
.image
width: 100%
height: $node-latest-thumbnail-size
min-height: $node-latest-thumbnail-size
max-height: $node-latest-thumbnail-size
background-color: $color-background
margin: 5px auto 10px auto
position: relative
overflow: hidden
border-radius: 0
img
max-height: $node-latest-thumbnail-size
+position-center-translate
i
color: rgba(white, .9)
font-size: 1.8em
position: absolute
bottom: 3px
left: 5px
text-shadow: 1px 1px 0 rgba(black, .2)
&.pi-file-archive
font-size: 1.5em
bottom: 5px
&.pi-newspaper
font-size: 1.6em
left: 7px
.ribbon
+ribbon
.info
width: 100%
height: 100%
display: flex
flex-direction: column
justify-content: space-between
word-break: break-word
.description
font-size: 1em
line-height: 1.8em
padding-top: 8px
color: $color-text-dark-primary
.title
display: block
font-size: 1.3em
color: $color-text-dark
font-weight: 600
+clearfix
+text-overflow-ellipsis
span.details
width: 100%
display: block
font-size: 1em
line-height: 1.2em
padding: 5px 0
color: $color-text-dark-secondary
+clearfix
.who
margin-left: 3px
.what
text-transform: capitalize
$bg-color: #444
$bg-color2: #666
$yellow: rgb(249,229,89)
$almost-white: rgb(255,255,255)
$btn-transparent-color: rgba(249,229,89,1)
$btn-transparent-bg: rgba(249,229,89,0)
section.gallery
max-width: 1024px
margin: 60px auto 0 auto
text-align: center
padding-bottom: 40px
p
color: $almost-white
padding: 0 40px
.thumbnail
float: left
position: relative
width: 23%
padding-bottom: 23%
margin: 0.83%
overflow: hidden
&:hover
box-shadow: 2px 2px 50px 0 rgba(0,0,0,0.3)
.img-container
position: absolute
width: 100%
height: 100%
img
width: 300%
transform: translate(-20%,-10%)
&:hover .img-caption
top: 0
left: 0
.btn-trans
background: rgba(255,255,255,0.4)
.img-caption
position: absolute
width: 100%
height: 100%
background: rgba(0, 0, 0, 0.3)
text-align: center
.table
display: table
.table-cell
display: table-cell
vertical-align: bottom
border: none
@media screen and (max-width: 992px)
.thumbnail
width: 22%
padding-bottom: 22%
margin: 1.5%
.img-container:hover .img-caption
top: 0
left: 0
.img-caption
position: absolute
width: 100%
height: 100%
background: rgba(0, 0, 0, .7)
text-align: center
a
color: $yellow
@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%

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

@@ -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 +1,26 @@
include ../../../../pillar/src/templates/mixins/components include ../mixins/components
| {# | {% macro navigation_tabs(title) %}
| Secondary Navigation Bars. +nav-secondary()
| #}
| {% 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( +nav-secondary-link(
class="{% if title == 'homepage' %}active{% endif %}",
href="{{ url_for('main.homepage') }}") href="{{ url_for('main.homepage') }}")
i.pi-blender-cloud-logo | Activity
+nav-secondary-link( +nav-secondary-link(
href="{{ url_for('cloud.open_projects') }}", class="{% if title == 'home' %}active{% endif %}",
class="{% if title == 'films' %}active{% endif %}") href="{{ url_for('projects.home_project') }}")
span Films | Home
+nav-secondary-link( +nav-secondary-link(
href="{{ url_for('cloud.learn') }}", class="{% if title == 'dashboard' %}active{% endif %}",
class="{% if title in ('learn', 'courses', 'workshops') %}active{% endif %}") href="{{ url_for('projects.index') }}")
span Training | My Projects
+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() %} | {% if current_user.has_organizations() %}
+nav-secondary-link( +nav-secondary-link(
href="{{ url_for('pillar.web.organizations.index') }}", class="{% if title == 'organizations' %}active{% endif %}",
class="{% if title == 'organizations' %}active{% endif %}") href="{{ url_for('pillar.web.organizations.index') }}")
i.pi-users.pr-2 | My Organizations
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 %} | {% endif %}
| {% endmacro %} | {% 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

@@ -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 our team to create more Open Projects, training, services and of course to make Blender the best
CG pipeline in the world. You rock! CG pipeline in the world. You rock!
p.buttons 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. p.
Here is a quick guide to help you get started with Blender Cloud. 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,10 +1,7 @@
| {% extends 'layout.html' %} | {% extends 'layout.html' %}
| {% from '_macros/_navigation.html' import navigation_homepage %} | {% from '_macros/_navigation.html' import navigation_tabs %}
| {% from '_macros/_asset_list_item.html' import asset_list_item %}
| {% from 'nodes/custom/blog/_macros.html' import render_blog_post %} | {% from 'nodes/custom/blog/_macros.html' import render_blog_post %}
include ../../../pillar/src/templates/mixins/components
| {% set title = 'homepage' %} | {% set title = 'homepage' %}
| {% block og %} | {% block og %}
@@ -22,57 +19,23 @@ meta(name="twitter:image", content="{% if main_project.picture_header %}{{ main_
| {% endblock %} | {% endblock %}
| {% block navigation_tabs %} | {% block navigation_tabs %}
| {{ navigation_homepage(title) }} | {{ navigation_tabs(title) }}
| {% endblock navigation_tabs %} | {% 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 %} | {% block body %}
.container-fluid.dashboard-container.imgs-fluid .container-fluid.dashboard-container.imgs-fluid
.row.mt-3 .row
.col-md-10.col-lg-9.col-xl-8.mx-auto .col-md-8
.d-xl-none section.blog
+featured_projects() ul.list-unstyled
| {% if latest_posts %}
+timeline() | {% for node in latest_posts %}
| {{ render_blog_post(node) }}
| {% endfor %}
| {% else %}
li
| No blog entries... yet!
| {% endif %}
.d-block.text-center .d-block.text-center
a.d-inline-block.p-3.text-muted(href="{{ url_for('main.main_blog') }}") a.d-inline-block.p-3.text-muted(href="{{ url_for('main.main_blog') }}")
@@ -86,61 +49,199 @@ mixin featured_projects()
i.pi-rss i.pi-rss
| RSS Feed | RSS Feed
.col-md-10.col-lg-9.col-xl-4.mx-auto .col-md-4
.d-lg-none.d-xl-block .dashboard-sidebar
+featured_projects()(class="card-deck-vertical border-bottom pb-3") section.pt-3
h6.title-underline In Production
a(href="/p/spring/")
img(src="{{ url_for('static', filename='assets/img/projects/spring_450x150.jpg')}}")
section.py-2.border-bottom.mb-3 p.text-muted.pt-2.
h6.title-underline A poetic short film about a mountain spirit and her wise little dog. #[a(href="/p/spring/") Check it out].
a.text-muted(href="{{ url_for('main.nodes_search_index') }}")
| Random Awesome
| {% if random_featured %} section.stream.py-3
+card-deck()(class='pl-3 random-featured') h6.title-underline Latest Assets
| {% for child in random_featured %}
| {% if child.node_type not in ['comment'] %} ul.activity-stream__list.list-unstyled
| {{ asset_list_item(child, current_user) }} | {% 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 %} | {% endif %}
| {% endfor %}
.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 %} | {% else %}
.card i.pi-folder
.card-body | {% endif %}
h6.card-title
| No random featured.
| {% endif %} | {% endif %}
section.py-3
h6.title-underline Latest Comments
ul.list-unstyled.pt-2 .activity-stream__list-details
| {% if latest_comments %} a.title(href="{{ n.url }}")
| {% for n in latest_comments %} | {{ n.name }}
li.pb-2.mb-2.border-bottom.text-truncate
a.js-comment-content.text-muted(href="{{ n.url }}") | {% if n.permissions.world %}
| {{ n.properties.content | striptags | truncate(200) }} .ribbon
span free
| {% endif %}
ul.list-unstyled.d-flex.text-muted
| {% 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 %} | {% if n.attached_to %}
.d-flex.align-items-baseline li.where-parent
a.text-muted.text-truncate(href="{{ n.attached_to.url }}") a(href="{{ n.attached_to.url }}") {{ n.attached_to.name }}
small.pr-2.font-weight-bold {{ n.project.name }} | {% endif %}
small {{ n.attached_to.name }} 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 %} | {% endif %}
.d-flex.align-items-baseline | {% if n.picture %}
small.pr-2.font-weight-bold {{ n.user.full_name }} ul.list-unstyled.d-flex.text-muted.extra
li.when
a(href="{{ n.url }}", title="{{ n._created }}") {{ n._created | pretty_date_time }}
li.who {{ n.user.full_name }}
| {% endif %}
| {% endfor %}
a.text-muted(href="{{ n.url }}", title="{{ n._created }}") li.activity-stream__list-item.empty#activity-stream__empty
small {{ n._created | pretty_date }} | No items to list.
section.random-asset.py-3
h6.title-underline
a(href="/search") Explore the Cloud
.pb-3.text-muted Random selection of the best assets &amp; tutorials
ul.random-asset__list.list-unstyled
| {% 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.list-unstyled.d-flex.text-muted
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.list-unstyled.d-flex.text-muted
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.list-unstyled.d-flex.text-muted
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.py-3
h6.title-underline Latest Comments
ul.list-unstyled
| {% 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.list-unstyled.d-flex.text-muted
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 %} | {% endfor %}
| {% else %} | {% else %}
span li.activity-stream__list-item.empty#activity-stream__empty
| No comments... yet! | No comments... yet!
| {% endif %} | {% endif %}
| {% endblock %} | {% endblock %}
@@ -149,10 +250,16 @@ script.
$(function () { $(function () {
/* cleanup mentions in comments */ /* cleanup mentions in comments */
$('.js-comment-content').each(function(){ $('.comment-content').each(function(){
$(this).text($(this).text().replace(/\*|\@|\<(.*?)\>/g, '')); $(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 hopToTop(); // Display jump to top button
}); });
| {% endblock %} | {% endblock %}

View File

@@ -1,5 +1,3 @@
include ../../../pillar/src/templates/mixins/components
doctype doctype
html(lang="en") html(lang="en")
head head
@@ -31,22 +29,27 @@ html(lang="en")
meta(name="twitter:image", content="{{ url_for('static', filename='assets/img/backgrounds/background_gleb_locomotive.jpg')}}") meta(name="twitter:image", content="{{ url_for('static', filename='assets/img/backgrounds/background_gleb_locomotive.jpg')}}")
| {% endblock og %} | {% endblock og %}
script(src="{{ url_for('static_pillar', filename='assets/js/tutti.min.js') }}") script(src="{{ url_for('static_pillar', filename='assets/js/vendor/jquery-3.1.0.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.typeahead-0.11.1.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')}}") script(src="{{ url_for('static_pillar', filename='assets/js/vendor/js.cookie-2.0.3.min.js')}}")
| {% if current_user.is_authenticated %} | {% if current_user.is_authenticated %}
script(src="{{ url_for('static_pillar', filename='assets/js/vendor/clipboard.min.js')}}") script(src="{{ url_for('static_pillar', filename='assets/js/vendor/clipboard.min.js')}}")
| {% endif %} | {% 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/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(href="{{ url_for('static', filename='assets/img/apple-touch-icon-precomposed.png') }}", rel="icon apple-touch-icon-precomposed", sizes="192x192")
| {% block head %}{% endblock %} | {% block head %}{% endblock %}
| {% block css %} | {% block css %}
link(href="{{ url_for('static_pillar', filename='assets/css/font-pillar.css') }}", rel="stylesheet")
| {% if title == 'blog' %} | {% if title == 'blog' %}
link(href="{{ url_for('static_pillar', filename='assets/css/blog.css') }}", rel="stylesheet") link(href="{{ url_for('static_pillar', filename='assets/css/blog.css') }}", rel="stylesheet")
| {% else %} | {% else %}
@@ -55,23 +58,11 @@ html(lang="en")
| {% endblock css %} | {% endblock css %}
| {% if not title %}{% set title="default" %}{% endif %} | {% if not title %}{% set title="default" %}{% endif %}
body(class="{{ title }} {{'project' if project and project.url != 'blender-cloud'}} {% block bodyclasses %}{% endblock %}" body(class="{{ title }}")
"{% block bodyattrs %}{% endblock %}"
)
| {% with messages = get_flashed_messages(with_categories=True) %} | {% with messages = get_flashed_messages(with_categories=True) %}
| {% if messages or (config.UI_ANNOUNCEMENT_NON_SUBSCRIBERS and not current_user.has_cap('subscriber')) %} | {% if messages %}
| {% 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 %} | {% for (category, message) in messages %}
.alert.d-flex.justify-content-center( .alert(role="alert", class="alert-{{ category }}")
role="alert",
class="alert-{{ category }}")
i.alert-icon(class="{{ category }}") i.alert-icon(class="{{ category }}")
span {{ message }} span {{ message }}
button.close(type="button", data-dismiss="alert") button.close(type="button", data-dismiss="alert")
@@ -81,24 +72,198 @@ html(lang="en")
| {% endwith %} | {% endwith %}
nav.navbar.navbar-expand-md.fixed-top.bg-white nav.navbar.navbar-expand-md.fixed-top.bg-white
| {% block navigation_tabs %} a.navbar-brand(
+nav-secondary(class="collapse navbar-collapse")#navigationLinks href="{{ url_for('main.homepage') }}",
+nav-secondary-link( title="Blender Cloud")
href="{{ url_for('main.homepage') }}") span.app-logo
i.pi-blender-cloud-logo i.pi-blender-cloud
| {% endblock navigation_tabs %}
+nav-secondary()(class="m-auto keep-when-overlay")
div.nav-item.quick-search.qs-input#qs-input
+nav-secondary()(class="ml-auto") button.navbar-toggler.text-light(
data-target=".navbar-collapse",
data-toggle="collapse",
type="button")
span.sr-only Toggle navigation
span.navbar-toggler-icon.d-flex.align-items-center
i.pi-menu
| {% block navigation_tabs %}
| {% endblock navigation_tabs %}
| {% block navigation_search %}
// TODO (pablo) - bring it back asap
.search-input
input#cloud-search(
type="text",
placeholder="Search assets, tutorials...")
i.search-icon.pi-search
| {% endblock navigation_search %}
.collapse.navbar-collapse
ul.navbar-nav.ml-auto
| {% if node and node.properties and node.properties.category %} | {% if node and node.properties and node.properties.category %}
| {% set category = node.properties.category %} | {% set category = node.properties.category %}
| {% else %} | {% else %}
| {% set category = title %} | {% set category = title %}
| {% endif %} | {% endif %}
li.nav-item.quick-search.cursor-pointer.px-3.pi-search#qs-toggle
| {% block navigation_sections %} | {% block navigation_sections %}
li
a.navbar-item(
href="{{ url_for('main.main_blog') }}",
title="Blender Cloud Blog",
data-toggle="tooltip",
data-placement="bottom",
class="{% if category == 'blog' %}active{% endif %}")
span Blog
li.dropdown
a.navbar-item.dropdown-toggle(
href="",
data-toggle="dropdown",
title="Libraries")
span Libraries
i.pi-angle-down
ul.dropdown-menu.p-0
li
a.navbar-item(
href="{{ url_for('projects.view', project_url='hdri') }}",
title="HDRI Library",
data-toggle="tooltip",
data-placement="left")
i.pi-globe
| HDRI
li
a.navbar-item(
href="{{ url_for('projects.view', project_url='textures') }}",
title="Textures Library",
data-toggle="tooltip",
data-placement="left")
i.pi-folder-texture
| Textures
li
a.navbar-item(
href="{{ url_for('projects.view', project_url='characters') }}",
title="Character Library",
data-toggle="tooltip",
data-placement="left")
i.pi-character
| Characters
li(class="dropdown")
a.navbar-item.dropdown-toggle(
href="{{ url_for('cloud.workshops') }}"
data-toggle="dropdown",
title="Training")
span Training
i.pi-angle-down
ul.dropdown-menu
li
a.navbar-item(
href="{{ url_for('cloud.courses') }}",
title="Courses",
data-toggle="tooltip",
data-placement="left")
i.pi-graduation-cap
| Courses
li
a.navbar-item(
href="{{ url_for('cloud.workshops') }}",
title="Workshops",
data-toggle="tooltip",
data-placement="left")
i.pi-lightbulb
| Workshops
li
a.navbar-item(
href="{{ url_for('projects.view', project_url='gallery') }}",
title="Curated artwork collection",
data-toggle="tooltip",
data-placement="left")
i.pi-image
| Art Gallery
li(class="dropdown")
a.navbar-item.dropdown-toggle(
href="{{ url_for('cloud.open_projects') }}",
title="Browse all the Open Projects",
data-toggle="dropdown",
class="{% if category in ['open-projects', 'film'] %}active{% endif %}")
span Open Projects
i.pi-angle-down
ul.dropdown-menu
li
a.navbar-item(href="/p/spring")
span.px-2 Spring
li
a.navbar-item(href="/p/hero")
span.px-2 Hero
li
a.navbar-item(href="/p/dailydweebs")
span.px-2 The Daily Dweebs
li
a.navbar-item(href="/p/agent-327")
span.px-2 Agent 327
li
a.navbar-item(href="/p/caminandes-3")
span.px-2 Caminandes: Llamigos
li.dropdown-divider
li
a.navbar-item(href="{{ url_for('cloud.open_projects') }}")
span.pl-2 All Open Projects
li(class="dropdown")
a.navbar-item.dropdown-toggle(
href="{{ url_for('cloud.services') }}",
title="Blender Cloud Services",
data-toggle="dropdown",
class="{% if category == 'services' %}active{% endif %}")
span Services
i.pi-angle-down
ul.dropdown-menu
li
a.navbar-item(
href="/attract",
title="Production Management",
data-toggle="tooltip",
data-placement="left")
i.pi-attract
| Attract
li
a.navbar-item(
href="/flamenco",
title="Render Management",
data-toggle="tooltip",
data-placement="left")
i.pi-flamenco
| Flamenco
li
a.navbar-item(
href="/services#blender-cloud-add-on",
title="Blender Sync, Texture Browser and more",
data-toggle="tooltip",
data-placement="left")
i.pi-blender
| Blender Cloud Add-on
li.dropdown-divider
li
a.navbar-item(
href="{{ url_for('cloud.services') }}",
title="All Blender Cloud services",
data-toggle="tooltip",
data-placement="left")
i.pi-list
| All Services
| {% endblock navigation_sections %} | {% endblock navigation_sections %}
| {% block navigation_user %} | {% block navigation_user %}
@@ -107,19 +272,14 @@ html(lang="en")
| {% endblock navigation_user %} | {% endblock navigation_user %}
| {% if current_user.is_anonymous %} | {% if current_user.is_anonymous %}
li li.pt-1
a.btn.btn-sm.btn-primary.px-4.mx-1( a.btn.btn-sm.btn-primary.px-3.mx-1(
href="https://store.blender.org/product/membership/", href="https://store.blender.org/product/membership/",
title="Sign up") Sign up title="Sign up") Sign up
| {% endif %} | {% endif %}
.loading-bar
.page-content .page-content
.quick-search.container-fluid.m-auto.p-5#search-overlay #search-overlay
ul.qs-loading.text-center
i.h1.pi-spin.spinner
h2 Loading
| {% block page_overlay %} | {% block page_overlay %}
#page-overlay #page-overlay
| {% endblock page_overlay %} | {% endblock page_overlay %}
@@ -127,7 +287,114 @@ html(lang="en")
| {% block body %}{% endblock %} | {% block body %}{% endblock %}
| {% block footer_container %} | {% block footer_container %}
| {% include '_footer.html' %} .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://twitter.com/Blender_Cloud",
title="Follow us on Twitter")
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
| TRAINING
ul.list-unstyled
li
a(href="{{ url_for('cloud.courses') }}")
| Courses
li
a(href="{{ url_for('cloud.workshops') }}")
| Workshops
li
a(href="{{ url_for('projects.view', project_url='gallery') }}")
| Art Gallery
.col-md-2.col-xs-6
h7.font-weight-bold
| LIBRARIES
ul.list-unstyled
li
a(href="{{ url_for('main.main_blog') }}",
title="Blender Cloud Blog")
| HDRIs
li
a(href="{{ url_for('cloud.services') }}",
title="Blender Cloud Services")
| Textures
li
a(href="{{ url_for('cloud.about') }}",
title="About Blender Cloud")
| Characters
.col-md-2.col-xs-6
h7.font-weight-bold
a(href="{{ url_for('cloud.services') }}")
| SERVICES
ul.list-unstyled
li
a(href="{{ url_for('main.main_blog') }}",
title="Blender Cloud Blog")
| Add-on
li
a(href="{{ url_for('main.main_blog') }}",
title="Blender Cloud Blog")
| Blender Sync
li
a(href="{{ url_for('cloud.services') }}",
title="Blender Cloud Services")
| Attract
li
a(href="{{ url_for('cloud.about') }}",
title="About Blender Cloud")
| Flamenco
li
a(href="{{ url_for('cloud.about') }}",
title="About Blender Cloud")
| Image Sharing
.col-md-2.col-xs-6
h7.font-weight-bold
| BLENDER
ul.list-unstyled
li
a(href="{{ url_for('main.main_blog') }}",
title="Blender Cloud Blog")
| blender.org
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
| {% endblock footer_navigation %}
#hop(title="Be awesome in space")
i.pi-angle-up
| {% endblock footer_container %} | {% endblock footer_container %}
#notification-pop(data-url="", data-read-toggle="") #notification-pop(data-url="", data-read-toggle="")
@@ -139,6 +406,9 @@ html(lang="en")
.nc-text .nc-text
span.nc-date span.nc-date
a(href="") a(href="")
script(src="{{ url_for('static_cloud', filename='assets/js/bootstrap.min.js') }}")
| {% if current_user.is_authenticated %} | {% if current_user.is_authenticated %}
script(src="{{ url_for('static_pillar', filename='assets/js/vendor/jquery.typewatch-3.0.0.min.js') }}") script(src="{{ url_for('static_pillar', filename='assets/js/vendor/jquery.typewatch-3.0.0.min.js') }}")
script. script.
@@ -163,43 +433,10 @@ html(lang="en")
{% endif %} {% endif %}
}); });
// Enable all tooltips.
if (typeof $().tooltip != 'undefined'){ if (typeof $().tooltip != 'undefined'){
$('[data-toggle="tooltip"]').tooltip({'delay' : {'show': 0, 'hide': 0}}); $('[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'}},
]
},
}
$('#qs-toggle').quickSearch({
resultTarget: '#search-overlay',
inputTarget: '#qs-input',
searches: searches,
});
| {% block footer_scripts_pre %}{% endblock %} | {% block footer_scripts_pre %}{% endblock %}
| {% block footer_scripts %}{% 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 %} | {% endif %}
| {% block menu_avatar %} | {% block menu_avatar %}
a.navbar-item.dropdown-toggle(href="{{ url_for('settings.profile') }}", data-toggle="dropdown") a.navbar-item.dropdown-toggle(href="#", data-toggle="dropdown", title="{{ current_user.email }}")
current-user-avatar img.gravatar(
src="{{ current_user.gravatar }}",
class="{{ subscription }}",
alt="Avatar")
.special(class="{{ subscription }}") .special(class="{{ subscription }}")
| {% if subscription == 'subscriber' %} | {% if subscription == 'subscriber' %}
i.pi-check i.pi-check
@@ -19,27 +22,25 @@ a.navbar-item.dropdown-toggle(href="{{ url_for('settings.profile') }}", data-tog
| {% else %} | {% else %}
i.pi-attention i.pi-attention
| {% endif %} | {% endif %}
script.
new Vue({el: 'current-user-avatar'})
| {% endblock menu_avatar %} | {% endblock menu_avatar %}
| {% block menu_list %} | {% block menu_list %}
li.subscription-status(class="{{ subscription }}") li.subscription-status(class="{{ subscription }}")
| {% if subscription == 'subscriber' %} | {% if subscription == 'subscriber' %}
a.navbar-item.pt-2.pl-2.pr-3( a.navbar-item(
href="{{url_for('settings.billing')}}" href="{{url_for('settings.billing')}}"
title="View subscription info") title="View subscription info")
i.pi-grin i.pi-grin
span.subitem Your subscription is active! span Your subscription is active!
| {% elif subscription == 'demo' %} | {% elif subscription == 'demo' %}
a.navbar-item.pt-2.pl-2.pr-3( a.navbar-item(
href="{{url_for('settings.billing')}}" href="{{url_for('settings.billing')}}"
title="View subscription info") title="View subscription info")
i.pi-heart-filled 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') %} | {% 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.navbar-item(target='_blank', href="/renew", title="Renew subscription")
i.pi-heart i.pi-heart
span.info Your subscription is not active. span.info Your subscription is not active.
span.renew Click here to renew. span.renew Click here to renew.
@@ -55,7 +56,7 @@ li.subscription-status(class="{{ subscription }}")
| {{ super() }} | {{ super() }}
li li
a.navbar-item.px-2( a.navbar-item(
href="{{ url_for('settings.billing') }}" href="{{ url_for('settings.billing') }}"
title="Billing") title="Billing")
i.pi-credit-card i.pi-credit-card

View File

@@ -1,33 +1,69 @@
//- Category listing (Learn, Libraries, etc). // {#
//- Header // Header of landing pages. title or text can be skipped:
mixin category_list_header(title, text) // +jumbotron("{{ page_title }}", null, "{{ page_header_image }}")
.row.pt-2.pb-3.mb-4.border-bottom&attributes(attributes) // Any extra attributes added (in a separate group) will be passed as is:
// +jumbotron("{{ page_title }}", null, "{{ page_header_image }}")(data-node-id='{{ node._id }}')
// #}
mixin jumbotron(title, text, image, url)
if url
a.jumbotron.jumbotron-overlay.text-white(
style='background-image: url(' + image + ');',
href=url)&attributes(attributes)
.container
.row
.col-md-9 .col-md-9
h1.py-2.font-weight-bold if title
.display-4.text-uppercase.font-weight-bold
=title =title
if text
.lead
=text
else
.jumbotron.jumbotron-overlay.text-white(style='background-image: url(' + image + ');')&attributes(attributes)
.container
.row
.col-md-9
if title
.display-4.text-uppercase.font-weight-bold
=title
if text
.lead .lead
=text =text
if block // {# Secondary navigation.
block // e.g. Workshops, Courses. #}
mixin nav-secondary(title)
//- List Item ul.nav.nav-secondary&attributes(attributes)
mixin category_list_item(title, text, url, image, image_link_url) if title
.row.pb-2.my-2&attributes(attributes) li.font-weight-bold.px-2
.col-md-8
a(href=url, title=title)
h3.font-weight-bold.text-muted
=title =title
.lead
=text
if block if block
block block
else
p No items defined.
.col-md-4 mixin nav-secondary-link()
if image_link_url li.nav-item
- var url = image_link_url a.nav-link&attributes(attributes)
block
a(href=url, title=title) // {# Takes as argument the number of columns to use in this deck. 1-6 #}
img.img-fluid.rounded(alt=title, src=image) mixin card-deck(columns)
.card-deck.card-padless(class='card-' + columns + '-columns')
if block
block
else
p No cards defined.
// {#
// Passes all attributes to the card.
// You can do fun stuff in a loop even like:
// +card(data-url="{{ url_for('projects.view', project_url=project.url) }}", tabindex='{{ loop.index }}')
// #}
mixin card()
.card.card-fade.cursor-pointer.mb-4.js-project-go&attributes(attributes)
if block
block
else
p No card content defined.

View File

@@ -1,25 +1,21 @@
include ../../../../../../pillar/src/templates/mixins/components include ../../../mixins/components
| {% import 'projects/_macros.html' as projectmacros %}
| {% macro render_blog_post(node, project=None, pages=None) %} | {% macro render_blog_post(node, project=None, pages=None) %}
.expand-image-links.imgs-fluid .expand-image-links.imgs-fluid
| {% if node.picture %} | {% if node.picture %}
+jumbotron( +jumbotron(
"{{ node.name }}", "{{ 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._created | pretty_date }}",
"{{ node.picture.thumbnail('h', api=api) }}", "{{ node.picture.thumbnail('h', api=api) }}",
"{{ node.url }}")( "{{ node.url }}")(class="row")
class="jumbotron-overlay")
| {% else %} | {% else %}
.pt-5.text-center.text-muted .pt-3.text-center.text-muted
h2.pb-2 h2
a.text-muted(href="{{ node.url }}") a.text-muted(href="{{ node.url }}")
| {{ node.name }} | {{ node.name }}
ul.d-flex.list-unstyled.justify-content-center 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 %} | {% if node.project.name %}
li.pr-2 {{ node.project.name }} li.pr-2 {{ node.project.name }}
| {% endif %} | {% endif %}
@@ -34,85 +30,83 @@ include ../../../../../../pillar/src/templates/mixins/components
li li
a.px-2(href="{{ node.url }}#comments") a.px-2(href="{{ node.url }}#comments")
| Leave a comment | Leave a comment
| {% if node.has_method('PUT') %}
li
a.px-2(href="{{url_for('nodes.edit', node_id=node._id)}}")
i.pi-edit
| Edit Post
| {% endif %}
| {% endif %} | {% endif %}
.node-details-description.mx-auto.py-5 | {% if project and project._id != config.MAIN_PROJECT_ID %}
| {{ projectmacros.render_secondary_navigation(project, pages=pages) }}
| {% endif %}
.row
.col-md-9.mx-auto
.item-content.pt-4
| {{ node.properties | markdowned('content') }} | {{ node.properties | markdowned('content') }}
hr.my-4 hr.my-4
comments-tree#comments-embed.justify-content-center.mx-auto(
parent-id="{{ node._id }}"
read-only=false
)
| {% endmacro %} | {% endmacro %}
//- ******************************************************* -// //- ******************************************************* -//
| {% macro render_blog_list_item(node) %} | {% macro render_blog_list_item(node) %}
a.card.asset.card-image-fade( .row.position-relative.py-2
href="{{ node.url }}") .col-md-1
.card-thumbnail
| {% if node.picture %} | {% if node.picture %}
img.card-img-top(src="{{ node.picture.thumbnail('m', api=api) }}", alt="{{ node.name }}") a.imgs-fluid(href="{{ node.url }}")
img(src="{{ node.picture.thumbnail('s', api=api) }}")
| {% else %} | {% else %}
.card-img-top .bg-primary.rounded.h-100
a.d-flex.align-items-center.justify-content-center.h-100.text-white(href="{{ node.url }}")
i.pi-document-text i.pi-document-text
| {% endif %} | {% endif %}
.card-body.py-2.d-flex.flex-column .col-md-11
.card-title.mb-1.font-weight-bold h5
| {{ node.name }} a.text-muted(href="{{ node.url }}") {{node.name}}
ul.card-text.list-unstyled.d-flex.text-black-50.mt-auto .text-muted.
li.pr-2 {{ node.user.full_name }} #[span(title="{{node._created}}") {{node._created | pretty_date }}]
li {{ node._created | pretty_date }} {% if node._created != node._updated %}
| {% if node.properties.status != 'published' %} #[span(title="{{node._updated}}") (updated {{node._updated | pretty_date }})]
li.text-info.font-weight-bold {{ node.properties.status}} {% endif %}
| {% endif %} {% if node.properties.category %} · {{node.properties.category}}{% endif %}
· {{node.user.full_name}}
{% if node.properties.status != 'published' %} · {{ node.properties.status}} {% endif %}
| {% endmacro %} | {% endmacro %}
//- ******************************************************* -// //- ******************************************************* -//
| {% macro render_blog_index(current_post, project, posts, can_create_blog_posts, api, more_posts_available, posts_meta, pages=None) %} | {% macro render_blog_index(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 %} | {% if can_create_blog_posts %}
+nav-secondary
+nav-secondary-link(href="{{url_for('nodes.posts_create', project_id=project._id)}}") +nav-secondary-link(href="{{url_for('nodes.posts_create', project_id=project._id)}}")
i.pi-plus.pr-2 span.text-success
span Create New Blog Post i.pi-plus
| {% endif %} | Create New Blog Post
| {% 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 %} | {% endif %}
| {% if posts %} | {% if posts %}
| {{ render_blog_post(current_post, project=project, pages=pages) }} | {{ render_blog_post(posts[0], project=project, pages=pages) }}
.container .container
.pt-4.text-center .row
h5 .col-md-9.mx-auto
| {% if more_posts_available %} | {% for node in posts[1:] %}
a.text-muted.py-3.d-block(href="{{ project.blog_archive_url }}") | {% if loop.first %}
| More from {{ project.name }} blog h5.text-muted.text-center Blasts from the past
| {% else %}
| More from {{ project.name }} blog
| {% endif %} | {% 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) }} | {{ render_blog_list_item(node) }}
| {% endif %}
| {% endfor %} | {% endfor %}
| {% if more_posts_available %} | {% if more_posts_available %}
a.d-block.pb-4.text-center(href="{{ project.blog_archive_url }}") .blog-archive-navigation
a(href="{{ project.blog_archive_url }}")
| {{posts_meta.total - posts|length}} more blog posts over here | {{posts_meta.total - posts|length}} more blog posts over here
i.pi-angle-right i.pi-angle-right
| {% endif %} | {% endif %}
@@ -128,29 +122,29 @@ a.card.asset.card-image-fade(
//- Macro for rendering the navigation buttons for prev/next pages -// //- Macro for rendering the navigation buttons for prev/next pages -//
| {% macro render_archive_pagination(project) %} | {% macro render_archive_pagination(project) %}
.d-flex.justify-content-center .blog-archive-navigation
| {% if project.blog_archive_prev %} | {% if project.blog_archive_prev %}
a.px-5.py-3( a.archive-nav-button(
href="{{ project.blog_archive_prev }}", rel="prev") href="{{ project.blog_archive_prev }}", rel="prev")
i.pi-angle-left i.pi-angle-left
| Previous page | Previous page
| {% else %} | {% else %}
span.px-5.py-3.text-black-50 span.archive-nav-button
i.pi-angle-left i.pi-angle-left
| Previous page | Previous page
| {% endif %} | {% endif %}
a.px-5.py-3( a.archive-nav-button(
href="{{ url_for('main.project_blog', project_url=project.url) }}") href="{{ url_for('main.project_blog', project_url=project.url) }}")
| Blog Index | Blog Index
| {% if project.blog_archive_next %} | {% if project.blog_archive_next %}
a.px-5.py-3( a.archive-nav-button(
href="{{ project.blog_archive_next }}", rel="next") href="{{ project.blog_archive_next }}", rel="next")
| Next page | Next page
i.pi-angle-right i.pi-angle-right
| {% else %} | {% else %}
span.px-5.py-3.text-black-50 span.archive-nav-button
| Next page | Next page
i.pi-angle-right i.pi-angle-right
| {% endif %} | {% endif %}
@@ -161,7 +155,6 @@ a.card.asset.card-image-fade(
| {{ render_archive_pagination(project) }} | {{ render_archive_pagination(project) }}
+card-deck(class="px-2")
| {% for node in posts %} | {% for node in posts %}
| {{ render_blog_list_item(node) }} | {{ render_blog_list_item(node) }}
| {% endfor %} | {% endfor %}

View File

@@ -1,6 +1,6 @@
| {% extends 'layout.html' %} | {% extends 'layout.html' %}
| {% from '_macros/_navigation.html' import navigation_home_project %} | {% from '_macros/_navigation.html' import navigation_tabs %}
include ../../../../pillar/src/templates/mixins/components include ../mixins/components
| {% set title = 'organizations' %} | {% set title = 'organizations' %}
| {% block page_title %}Organizations{% endblock %} | {% block page_title %}Organizations{% endblock %}
@@ -16,8 +16,9 @@ meta(property="og:image", content="{{ url_for('static', filename='assets/img/bac
meta(name="twitter: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 %} | {% endblock %}
| {% block navigation_tabs %} | {% block navigation_tabs %}
| {{ navigation_home_project(title) }} | {{ navigation_tabs(title) }}
| {% endblock navigation_tabs %} | {% endblock navigation_tabs %}
| {% block body %} | {% block body %}
@@ -26,8 +27,8 @@ meta(name="twitter:image", content="{{ url_for('static', filename='assets/img/ba
+nav-secondary-link( +nav-secondary-link(
class="create", class="create",
onclick='createNewOrganization(this)') onclick='createNewOrganization(this)')
i.pi-plus.text-success
span.text-success span.text-success
i.pi-plus
| Create Organization | Create Organization
| {% endif %} | {% endif %}
@@ -53,7 +54,7 @@ meta(name="twitter:image", content="{{ url_for('static', filename='assets/img/ba
ul.meta ul.meta
li(title="Members") li(title="Members")
| {{ organization.members|hide_none|count }} Member{{ organization.members|hide_none|count|pluralize }} | {{ organization.members|hide_none|count }} Member{{ organization.members|hide_none|count|pluralize }}
| {% if (organization.unknown_members|hide_none|count) != 0 %} | {% if (organization.unknown_members|count) != 0 %}
| ({{ organization.unknown_members|hide_none|count }} pending) | ({{ organization.unknown_members|hide_none|count }} pending)
| {% endif %} | {% endif %}
li(title="Seats") li(title="Seats")

View File

@@ -15,7 +15,7 @@ style.
This Application collects some Personal Data from its Users. This Application collects some Personal Data from its Users.
h3 Data Controller and Owner h3 Data Controller and Owner
p. 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 institute@blender.org
p. p.
Blender Institute has been authorised by Stichting Blender Foundation to conduct these 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 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. policy applies to all Personal Data the Data Controller has about Users.
h4 Definitions and legal references h4 Definitions and legal references
p Original issue: February 27, 2014 p Latest update: February 27, 2014
p Latest update: June 10, 2019 (Updated Blender Institute address)
| {% endblock body%} | {% endblock body%}

View File

@@ -1,64 +1,35 @@
| {% extends 'layout.html' %} | {% extends 'layout.html' %}
| {% from '_macros/_navigation.html' import navigation_homepage %} | {% block page_title %}Production of Stuff{% endblock %}
| {% 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 %} | {% block head %}
script(src="{{ url_for('static_cloud', filename='assets/js/tagged_assets.min.js') }}") script(src="{{ url_for('static_cloud', filename='assets/js/tagged_assets.min.js') }}")
script. script.
$(function() { $(function() {
let is_subscriber = {{ current_user.has_cap('subscriber')|string|lower }}; $('ul.asset-list').loadTaggedAssets();
$('.js-asset-list').loadTaggedAssets(8, 8, is_subscriber); })
});
| {% endblock %} | {% endblock %}
| {% block body %} | {% block body %}
.container.py-4 #page-container
+category_list_header('{{ page_title }}', '{{ page_description }}') #page-content
.row h2 Production of Stuff
.col-12 p.
+group('Walk-through', 'walk-through') Here are our hand-selected assets 'bout stuff.
+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( h3 Animation
href="{{ url_for('main.nodes_search_index') }}") ul.asset-list(data-asset-tag="animation")
| Search Blender Cloud to find even more content
i.pi-angle-right.pl-1
h3 Modelling
ul.asset-list(data-asset-tag="modelling")
h3 Rigging
ul.asset-list(data-asset-tag="rigging")
h3 pipeline
ul.asset-list(data-asset-tag="pipeline")
h3 lookdev
ul.asset-list(data-asset-tag="lookdev")
h3 crazyspace
ul.asset-list(data-asset-tag="crazyspace")
| {% endblock body%} | {% 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,6 +1,4 @@
| {% extends 'projects/home_layout.html' %} | {% extends 'projects/home_layout.html' %}
| {% set title = 'blender-sync' %}
| {% set subtab = 'blender_sync' %} | {% set subtab = 'blender_sync' %}
| {% set learn_more_btn_url = '/blog/introducing-blender-sync' %} | {% set learn_more_btn_url = '/blog/introducing-blender-sync' %}
| {% block currenttab %} | {% block currenttab %}

View File

@@ -1,6 +1,8 @@
| {% extends 'layout.html' %} | {% extends 'layout.html' %}
| {% from '_macros/_navigation.html' import navigation_home_project %} | {% from '_macros/_navigation.html' import navigation_tabs %}
include ../../../../pillar/src/templates/mixins/components include ../mixins/components
| {% set title = 'home' %}
| {% block og %} | {% block og %}
meta(property="og:type", content="website") meta(property="og:type", content="website")
@@ -18,13 +20,20 @@ meta(name="twitter:image", content="{{ url_for('static', filename='assets/img/ba
| {% endblock %} | {% endblock %}
| {% block navigation_tabs %} | {% block navigation_tabs %}
| {{ navigation_home_project(title) }} | {{ navigation_tabs(title) }}
| {% endblock navigation_tabs %} | {% endblock navigation_tabs %}
| {% block body %} | {% block body %}
.dashboard-container .dashboard-container
section#projects.bg-white section#projects.bg-white
+nav-secondary()(id='sub-nav-tabs__list')
+nav-secondary-link(id="subtab-blender_sync", data-tab-url="{{ url_for('projects.home_project')}}")
| Blender Sync
+nav-secondary-link(id="subtab-images", data-tab-url="{{ url_for('projects.home_project_shared_images')}}")
| Images
| {% block currenttab %}{% endblock %} | {% block currenttab %}{% endblock %}
| {% endblock %} | {% endblock %}

View File

@@ -1,6 +1,6 @@
| {% extends 'layout.html' %} | {% extends 'layout.html' %}
| {% from '_macros/_navigation.html' import navigation_home_project %} | {% from '_macros/_navigation.html' import navigation_tabs %}
include ../../../../pillar/src/templates/mixins/components include ../mixins/components
| {% set title = 'dashboard' %} | {% set title = 'dashboard' %}
@@ -39,45 +39,41 @@ style.
| {% endblock %} | {% endblock %}
| {% block navigation_tabs %} | {% block navigation_tabs %}
| {{ navigation_home_project(title) }} | {{ navigation_tabs(title) }}
| {% endblock navigation_tabs %} | {% endblock navigation_tabs %}
| {% block body %} | {% block body %}
.dashboard-container .dashboard-container
section.dashboard-main section.dashboard-main
section#projects.bg-white section#projects.bg-white
.d-flex
+nav-secondary()(id='sub-nav-tabs__list') +nav-secondary()(id='sub-nav-tabs__list')
+nav-secondary-link(data-tab-toggle='own_projects', class="active") +nav-secondary-link(data-tab-toggle='own_projects', class="active")
span
| Own Projects | Own Projects
| {% if projects_user|length != 0 %} | {% if projects_user|length != 0 %}
.d-inline.text-muted.pl-1 ({{ projects_user|length }}) span ({{ projects_user|length }})
| {% endif %} | {% endif %}
+nav-secondary-link(data-tab-toggle='shared') +nav-secondary-link(data-tab-toggle='shared')
span
| Shared with me | Shared with me
| {% if projects_shared|length != 0 %} | {% if projects_shared|length != 0 %}
.d-inline.text-muted.pl-1 ({{ projects_shared|length }}) span ({{ projects_shared|length }})
| {% endif %} | {% endif %}
+nav-secondary()()
| {% if current_user.has_cap('subscriber') %} | {% if current_user.has_cap('subscriber') %}
+nav-secondary-link( +nav-secondary-link(
id="project-create", id="project-create",
data-url="{{ url_for('projects.create') }}", data-url="{{ url_for('projects.create') }}",
href="{{ url_for('projects.create') }}") href="{{ url_for('projects.create') }}")
span.text-success Create New Project... span.text-success
| #[i.pi-plus] Create New Project
| {% elif current_user.has_cap('can-renew-subscription') %} | {% elif current_user.has_cap('can-renew-subscription') %}
+nav-secondary-link( +nav-secondary-link(
id="project-create", id="project-create",
data-url="{{ url_for('projects.create') }}", data-url="{{ url_for('projects.create') }}",
href="/renew", href="/renew",
target="_blank") target="_blank")
i.pi-heart-filled.text-danger.pr-1 | #[i.pi-heart-filled.text-danger] Resubscribe to Create a Project
span Resubscribe to Create a Project
| {% endif %} | {% endif %}
nav.nav-tabs__tab.active#own_projects nav.nav-tabs__tab.active#own_projects

View File

@@ -1,25 +1,68 @@
| {% import 'projects/_macros.html' as projectmacros %}
| {% extends 'layout.html' %} | {% 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 page_title %}{{ project.name }}{% endblock%}
| {% block og %}
meta(property="og:type", content="website")
| {% if og_picture %} | {% if og_picture %}
| {% set og_picture_url = og_picture.thumbnail('l', api=api) %} meta(property="og:image", content="{{ og_picture.thumbnail('l', api=api) }}")
| {% else %} meta(name="twitter:image", content="{{ og_picture.thumbnail('l', api=api) }}")
| {% set og_picture_url = None %} | {% 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 %} | {% endif %}
| {% block og %} | {% if show_project %}
| {{ opengraph(project.name, project.summary, og_picture_url, url_for('cloud.project_landing', project_url=project.url, _external=True)) }} meta(property="og:title", content="{{ project.name }} - Blender Cloud")
| {% endblock %} 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, _external=True)}}")
| {% 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 %} | {% block page_overlay %}
#page-overlay.video #page-overlay.video
@@ -27,156 +70,107 @@ include ../../../../pillar/src/templates/mixins/components
#others #others
| {% endblock %} | {% endblock %}
| {% block css %} | {% block head %}
link(href="{{ url_for('static_cloud', filename='assets/css/project-main.css') }}", rel="stylesheet")
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') }}")
| {% endblock %} | {% endblock %}
| {% block navigation_tabs %} | {% block css %}
| {{ navigation_project(project, navigation_links, extension_sidebar_links, title) }} link(href="{{ url_for('static_cloud', filename='assets/css/project-landing.css') }}", rel="stylesheet")
| {% endblock navigation_tabs %} | {% endblock %}
| {% block body %} | {% block body %}
header
//a(href="{{ url_for( 'projects.view', project_url=project.url) }}")
img.header(src="{{ project.picture_header.thumbnail('h', api=api) }}")
| {% if project.has_method('PUT') %} | {% block navbar_secondary %}
+nav-secondary | {{ projectmacros.render_secondary_navigation(project, pages=pages) }}
+nav-secondary-link(
href="{{ url_for('projects.edit', project_url=project.url) }}",
class="text-white")
i.pi-edit.pr-2
span Edit Project
| {% endif %}
| {% if project.picture_header %} | {% endblock navbar_secondary %}
| {% set project_header = project.picture_header.thumbnail('h', api=api) %} #container.landing
| {% endif %} section.node-details-container.project
.node-details-title
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 %} | {% if project.description %}
.node-details-description
| {{ project | markdowned('description') }} | {{ project | markdowned('description') }}
| {% endif %} | {% endif %}
.row
.col-md-10.mx-auto section.gallery
section.py-5 h2 Gallery
.gallery.mx-auto
| {% for n in activity_stream %} | {% for n in activity_stream %}
| {% if n.node_type not in ['comment', 'post'] and n.picture %} | {% if n.node_type not in ['comment', 'post'] and n.picture %}
.thumbnail.expand-image-links .thumbnail.expand-image-links
.img-container .img-container
a.js-open-overlay-image( a(href="{{ n.picture.thumbnail('l', api=api) }}", data-node_id="{{ n._id }}")
title="{{ n.name }}", img(src="{{ n.picture.thumbnail('l', api=api) }}", alt="{{ n.name }}")
href="{{ n.picture.thumbnail('l', api=api) }}") .img-caption.table
img( | {# Not using for the moment
alt="{{ n.name }}", span.table-cell {{ n.name }}
src="{{ n.picture.thumbnail('l', api=api) }}") | #}
| {% endif %} | {% endif %}
| {% endfor %} | {% endfor %}
div(class="clearfix")
.clearfix | {% if project.nodes_featured %}
.text-center.mx-auto.py-3 | {# In some cases featured_nodes might might be embedded #}
a.btn.btn-outline-primary.px-5( | {% if '_id' in project.nodes_featured[0] %}
href="{{ project_browse_url }}") | {% set featured_node_id=project.nodes_featured[0]._id %}
| See More Artwork | {% else %}
| {% set featured_node_id=project.nodes_featured[0] %}
| {% endif %}
a.btn(href="{{ url_for('projects.view_node', project_url=project.url, node_id=featured_node_id) }}") See more
| {% endif %}
.row.mt-5 section.node-extra
.col-md-10.mx-auto h2 Latest Updates
h2.pb-3 Project Timeline
.timeline-dark | {% if activity_stream %}
+timeline("{{ project._id }}") .node-updates
ul.node-updates-list
| {% for n in activity_stream %}
| {% if n.node_type == 'post' %}
li.node-updates-list-item(
data-node_id="{{ n._id }}",
class="{{ n.node_type }} {{ n.properties.content_type | hide_none }}")
a.image(href="{{ url_for_node(node=n) }}")
| {% if n.picture %}
img(src="{{ n.picture.thumbnail('l', api=api) }}")
| {% endif %}
.info
a.title(href="{{ url_for_node(node=n) }}") {{ n.name }}
p.description(href="{{ url_for_node(node=n) }}")
| {% if n.node_type == 'post' %}
| {{ n.properties | markdowned('content') | striptags | truncate(140, end="... <small>read more</small>") | safe | hide_none }}
| {% else %}
| {{ n | markdowned('description') | striptags | truncate(140, end="... <small>read more</small>") | safe | hide_none }}
| {% endif %}
//span.details
// span.what {% if n.properties.content_type %}{{ n.properties.content_type | undertitle }}{% else %}{{ n.node_type | undertitle }}{% endif %} ·
// span.when {{ n._updated | pretty_date }} by
// span.who {{ n.user.full_name }}
| {% endif %}
| {% endfor %}
| {% endif %}
a.btn(href="{{ url_for('main.project_blog', project_url=project.url) }}") See all updates
| {% endblock body %} | {% endblock body %}
| {% block footer_scripts %} | {% block footer_scripts %}
script. script.
function showOverlay(html_content) { // Click anywhere in the page to hide the overlay
$('#page-overlay')
.addClass('active')
.html(html_content);
}
function hideOverlay() { function hideOverlay() {
$('#page-overlay') $('#page-overlay.video').removeClass('active');
.removeClass('active') $('#page-overlay.video .video-embed').html('');
.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 () { $(document).click(function () {
hideOverlay(); hideOverlay();
}); });
@@ -186,4 +180,16 @@ script.
hideOverlay(); 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 %} | {% endblock %}

View File

@@ -1,14 +1,9 @@
| {% extends 'layout.html' %} | {% extends 'layout.html' %}
| {% from '_macros/_add_new_menu.html' import add_new_menu %} | {% from '_macros/_add_new_menu.html' import add_new_menu %}
| {% from '_macros/_navigation.html' import navigation_project %} include ../mixins/components
include ../../../../pillar/src/templates/mixins/components
| {% block page_title %}{{ project.name }}{% endblock%} | {% block page_title %}{{ project.name }}{% endblock%}
| {% if title is not defined %}
| {% set title = 'project' %} | {% set title = 'project' %}
| {% endif %}
| {% block og %} | {% block og %}
meta(property="og:type", content="website") meta(property="og:type", content="website")
@@ -78,153 +73,220 @@ link(href="{{ url_for('static_pillar', filename='assets/jstree/themes/default/st
link(rel="amphtml", href="{{ url_for('nodes.view', node_id=node._id, _external=True, format='amp') }}") link(rel="amphtml", href="{{ url_for('nodes.view', node_id=node._id, _external=True, format='amp') }}")
| {% endif %} | {% endif %}
script(src="{{ url_for('static_pillar', filename='assets/js/vendor/video.min.js') }}") 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-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_pillar', filename='assets/js/vendor/videojs-hotkeys-0.2.20.min.js') }}")
script(src="{{ url_for('static_pillar', filename='assets/js/video_plugins.min.js') }}") script(src="{{ url_for('static_pillar', filename='assets/js/video_plugins.min.js') }}")
| {% endblock %} | {% endblock %}
| {% block css %} | {% block css %}
link(href="{{ url_for('static_cloud', filename='assets/css/project-main.css') }}", rel="stylesheet") link(href="{{ url_for('static_pillar', filename='assets/css/font-pillar.css') }}", rel="stylesheet")
link(href="{{ url_for('static_pillar', filename='assets/css/project-main.css') }}", rel="stylesheet")
| {% endblock %} | {% endblock %}
| {% block navigation_tabs %} | {% block navigation_tabs %}
| {{ navigation_project(project, navigation_links, extension_sidebar_links, title) }} +nav-secondary()(class="bg-white")
| {% if project.category == 'course' %}
li.text-capitalize
a.nav-link.text-muted.px-0(href="{{ url_for('cloud.courses') }}")
| Courses
| {% elif project.category == 'workshop' %}
li.text-capitalize
a.nav-link.text-muted.px-0(href="{{ url_for('cloud.workshops') }}")
| Workshops
li.px-1
i.pi-angle-right
| {% endif %}
+nav-secondary-link(
class="px-0",
href="{{url_for('projects.view', project_url=project.url, _external=True)}}")
| {{ project.name }}
| {% if project.category == "open_project" %}
+nav-secondary-link(
class="active",
href="{{url_for('projects.view', project_url=project.url, _external=True)}}")
| Explore
+nav-secondary-link(
href="{{url_for('projects.view', project_url=project.url, _external=True)}}")
| Blog
+nav-secondary-link(
href="{{url_for('projects.view', project_url=project.url, _external=True)}}")
| About
+nav-secondary-link(
href="{{url_for('projects.view', project_url=project.url, _external=True)}}")
| Team
+nav-secondary-link(
href="{{url_for('projects.view', project_url=project.url, _external=True)}}")
| Awards
| {% endif %}
| {% endblock navigation_tabs %} | {% endblock navigation_tabs %}
| {% block body %} | {% block body %}
#project-container.is-sidebar-visible #project-container
#project-side-container.bg-light #project-side-container
#project_sidebar.bg-white
ul.project-tabs.p-0
//- li.tabs-thumbnail(class="{% if project.picture_square %}image{% endif %}")
//- a(href="{{url_for('projects.view', project_url=project.url)}}")
//- #project-loading
//- i.pi-spin
//- | {% if project.picture_square %}
//- img(src="{{ project.picture_square.thumbnail('b', api=api) }}")
//- | {% else %}
//- i.pi-home
//- | {% endif %}
li.tabs-browse(
title="Browse",
data-toggle="tooltip",
data-placement="right",
class="active")
a(href="{{url_for('projects.view', project_url=project.url, _external=True)}}")
i.pi-folder
| {% if not project.is_private %}
| {% if current_user_is_subscriber %}
li.tabs-search(
title="Search",
data-toggle="tooltip",
data-placement="right")
a(href="{{ url_for('projects.search', project_url=project.url, _external=True)}} ")
i.pi-search
| {% else %}
li.tabs-search(
title="Search (subscribers only)",
data-toggle="tooltip",
data-placement="right")
a(href="{{ url_for('cloud.join') }}")
i.pi-search
| {% endif %}
| {% endif %}
| {{ extension_sidebar_links }}
| {% if project.has_method('PUT') %}
li(
title="Edit Project",
data-toggle="tooltip",
data-placement="right")
a(href="{{ url_for('projects.edit', project_url=project.url) }}")
i.pi-cog
| {% endif %}
#project_nav(class="{{ title }}") #project_nav(class="{{ title }}")
#project_nav-container #project_nav-container
| {% if title != 'about' %} | {% if title != 'about' %}
button.project-sidebar-toggle.btn.btn-sm.btn-link.px-1.rounded-0.bg-light.text-muted.position-absolute( //- +nav-secondary(class="bg-white")
type="button", //- +nav-secondary-link(
class="js-project-sidebar-toggle") //- class="active",
i.pi-angle-double-left //- href="{{url_for('projects.view', project_url=project.url, _external=True)}}")
//- | {{ project.name }}
//- #project_nav-header.bg-white
//- a.project-title.p-2.font-weight-bold.text-dark(
//- href="{{url_for('projects.view', project_url=project.url, _external=True)}}")
//- | {{ project.name }}
| {% block project_tree %} | {% block project_tree %}
#project_tree.bg-light.p-1 #project_tree.bg-white
| {% endblock project_tree %} | {% endblock project_tree %}
| {% endif %} | {% endif %}
#project_context-container
.breadcrumbs-container.bg-dark.fixed-top
button.project-sidebar-toggle.btn.btn-sm.btn-link.px-1.bg-dark.rounded-0.text-muted(
type="button",
class="js-project-sidebar-toggle")
i.pi-menu
node-breadcrumbs(node-id="{{ node._id }}", @navigate="(nodeId)=>{displayNode(nodeId)}")
script.
new Vue({el:'node-breadcrumbs'});
#project_context-container.border-left
| {% if project.has_method('PUT') %} | {% if project.has_method('PUT') %}
#project_context-header.position-absolute #project_context-header.bg-white
ul.project-edit-tools.disabled.d-flex.list-unstyled.py-2.mb-0 span#status-bar
li.dropdown(
title="Create...", ul.project-edit-tools.disabled
data-toggle="tooltip", li.dropdown
data-placement="left") button#item_add.project-mode-view.btn.btn-sm.btn-outline-secondary.dropdown-toggle(
button.dropdown-toggle.btn.btn-sm.btn-outline-secondary(
id="item_add",
class="project-mode-view",
type="button", type="button",
data-toggle="dropdown", data-toggle="dropdown",
aria-haspopup="true", aria-haspopup="true",
aria-expanded="false") aria-expanded="false")
i.pi-collection-plus i.button-add-icon.pi-collection-plus
| New...
ul.dropdown-menu.dropdown-menu-right( ul.dropdown-menu.add_new-menu
class="add_new-menu")
| {{ add_new_menu(project.node_types) }} | {{ add_new_menu(project.node_types) }}
li.button-edit li.button-edit
a.btn.btn-sm.btn-outline-secondary.ml-2.px-2( a#item_edit.project-mode-view.btn.btn-sm.btn-outline-secondary.ml-2(
id="item_edit",
class="project-mode-view",
href="javascript:void(0);", href="javascript:void(0);",
title="Edit", title="Edit",
data-project_id="{{project._id}}", data-project_id="{{project._id}}")
data-toggle="tooltip",
data-placement="top")
i.button-edit-icon.pi-edit i.button-edit-icon.pi-edit
| Edit Project
li.dropdown li.dropdown
button.dropdown-toggle.btn.btn-sm.btn-outline-secondary.mx-2( button.dropdown-toggle.project-mode-view.btn.btn-sm.btn-outline-secondary.mx-2(
class="project-mode-view",
type="button", type="button",
data-toggle="dropdown", data-toggle="dropdown",
aria-haspopup="true", aria-haspopup="true",
aria-expanded="false") aria-expanded="false")
i.pi-more-vertical.p-0 i.pi-more-vertical.p-0
ul.dropdown-menu.dropdown-menu-right ul.dropdown-menu
| {% if current_user.has_cap('admin') %} | {% if current_user.has_cap('admin') %}
li li.dropdown-item
a.dropdown-item( a#item_featured(
id="item_featured",
href="javascript:void(0);", href="javascript:void(0);",
title="Feature on project's homepage", title="Feature on project's homepage",
data-toggle="tooltip", data-toggle="tooltip",
data-placement="left") data-placement="left")
i.pi-star.pr-2 i.pi-star
| Toggle Featured | Toggle Featured
li li.dropdown-item
a.dropdown-item( a#item_toggle_public(
id="item_toggle_public",
href="javascript:void(0);", href="javascript:void(0);",
title="Make it accessible to anyone", title="Make it accessible to anyone",
data-toggle="tooltip", data-toggle="tooltip",
data-placement="left") data-placement="left")
i.pi-lock-open.pr-2 i.pi-lock-open
| Toggle Public | Toggle Public
| {% endif %} | {% endif %}
li li.dropdown-item
a.dropdown-item( a#item_toggle_projheader(
id="item_toggle_projheader",
href="javascript:void(0);", href="javascript:void(0);",
title="Feature as project's header", title="Feature as project's header",
data-toggle="tooltip", data-toggle="tooltip",
data-placement="left") data-placement="left")
i.pi-star.pr-2 i.pi-star
| Toggle Project Header video | Toggle Project Header video
li.button-move li.dropdown-item.button-move
a.dropdown-item( a#item_move(
id="item_move",
href="javascript:void(0);", href="javascript:void(0);",
title="Move into a folder...", title="Move into a folder...",
data-toggle="tooltip", data-toggle="tooltip",
data-placement="left") data-placement="left")
i.button-move-icon.pi-move.pr-2 i.button-move-icon.pi-move
| Move | Move
li.button-delete li.dropdown-item.button-delete
a.dropdown-item( a#item_delete(
id="item_delete",
href="javascript:void(0);", href="javascript:void(0);",
title="Can be undone within a month", title="Can be undone within a month",
data-toggle="tooltip", data-toggle="tooltip",
data-placement="left") data-placement="left")
i.pi-trash.pr-2 i.pi-trash
| Delete Project | Delete Project
// Edit Mode // Edit Mode
li.button-cancel li.button-cancel
a.btn.btn-outline-secondary( a#item_cancel.project-mode-edit.btn.btn-outline-secondary(
id="item_cancel",
class="project-mode-edit",
href="javascript:void(0);", href="javascript:void(0);",
title="Cancel changes") title="Cancel changes")
i.button-cancel-icon.pi-cancel i.button-cancel-icon.pi-cancel
| Cancel | Cancel
li.button-save li.button-save
a.btn.btn-outline-success.mx-2( a#item_save.project-mode-edit.btn.btn-outline-success.mx-2(
id="item_save",
class="project-mode-edit",
href="javascript:void(0);", href="javascript:void(0);",
title="Save changes") title="Save changes")
i.button-save-icon.pi-check i.button-save-icon.pi-check
@@ -240,8 +302,7 @@ link(href="{{ url_for('static_cloud', filename='assets/css/project-main.css') }}
| {% endif %} | {% endif %}
#project_context #project_context
| {% block project_context %} | {% block project_context %}
| {% if show_project and not browse %} | {% if show_project %}
| {# Embed the project view only if we are not exploring it. #}
| {% include "projects/view_embed.html" %} | {% include "projects/view_embed.html" %}
| {% endif %} | {% endif %}
| {% endblock project_context %} | {% endblock project_context %}
@@ -270,13 +331,6 @@ script(type="text/javascript", src="{{ url_for('static_pillar', filename='assets
| {% endif %} | {% endif %}
script. script.
loadProjectSidebar();
$('body').on('click', '.js-project-sidebar-toggle', function(e){
e.preventDefault();
toggleProjectSidebar();
});
function updateToggleProjHeaderMenuItem() { function updateToggleProjHeaderMenuItem() {
var $toggle_projheader = $('#item_toggle_projheader'); var $toggle_projheader = $('#item_toggle_projheader');
@@ -334,7 +388,7 @@ script.
// TODO: Maybe remove this, now it's also in loadNodeContent(), but double-check // TODO: Maybe remove this, now it's also in loadNodeContent(), but double-check
// it's done like that in all users of updateUi(). // it's done like that in all users of updateUi().
loadingBarHide(); $('#project-loading').removeAttr('class');
} }
| {% endblock %} | {% endblock %}
@@ -362,36 +416,31 @@ script.
function loadNodeContent(url, nodeId) { function loadNodeContent(url, nodeId) {
$('#project-loading').addClass('active');
var $projectContext = $('#project_context')
$projectContext.trigger('pillar:workStart')
$.get(url, function(dataHtml) { $.get(url, function(dataHtml) {
// Update the DOM injecting the generate HTML into the page // Update the DOM injecting the generate HTML into the page
$projectContext.html(dataHtml); $('#project_context').html(dataHtml);
}) })
.done(function(){ .done(function(){
pillar.events.Nodes.triggerLoaded(nodeId);
updateUi(nodeId, 'view'); updateUi(nodeId, 'view');
}) })
.fail(function(dataResponse) { .fail(function(dataResponse) {
$projectContext.html($('<iframe id="server_error"/>')); $('#project_context').html($('<iframe id="server_error"/>'));
$('#server_error').attr('src', url); $('#server_error').attr('src', url);
}) })
.always(function(){ .always(function(){
$projectContext.trigger('pillar:workStop') $('#project-loading').removeAttr('class');
$('.button-edit-icon').addClass('pi-edit').removeClass('pi-spin spin'); $('.button-edit-icon').addClass('pi-edit').removeClass('pi-spin spin');
}); });
} }
function loadProjectContent(url) { function loadProjectContent(url) {
var $projectContext = $('#project_context') $('#project-loading').addClass('active');
$projectContext.trigger('pillar:workStart')
$.get(url, function(dataHtml) { $.get(url, function(dataHtml) {
// Update the DOM injecting the generated HTML into the page // Update the DOM injecting the generated HTML into the page
$projectContext.html(dataHtml); $('#project_context').html(dataHtml);
}) })
.done(function() { .done(function() {
updateUi('', 'view'); updateUi('', 'view');
@@ -399,11 +448,11 @@ script.
addMenuDisable(['texture']); addMenuDisable(['texture']);
}) })
.fail(function(dataResponse) { .fail(function(dataResponse) {
$projectContext.html($('<iframe id="server_error"/>')); $('#project_context').html($('<iframe id="server_error"/>'));
$('#server_error').attr('src', url); $('#server_error').attr('src', url);
}) })
.always(function(){ .always(function(){
$projectContext.trigger('pillar:workStop') $('#project-loading').removeAttr('class');
$('.button-edit-icon').addClass('pi-edit').removeClass('pi-spin spin'); $('.button-edit-icon').addClass('pi-edit').removeClass('pi-spin spin');
}); });
} }
@@ -497,11 +546,6 @@ script.
} }
$('.project-mode-view').displayAs('inline-block'); $('.project-mode-view').displayAs('inline-block');
$('.project-mode-edit').hide(); $('.project-mode-edit').hide();
{% if browse %}
let url = "{{url_for('cloud.project_browse_view_nodes', project_url=project.url)}}";
loadProjectContent(url);
{% endif %}
} else { } else {
displayNode(nodeId, false); displayNode(nodeId, false);
} }
@@ -549,7 +593,7 @@ script.
"image" : {"icon": "pi-image", "max_children": 0}, "image" : {"icon": "pi-image", "max_children": 0},
"hdri" : {"icon": "pi-globe", "max_children": 0}, "hdri" : {"icon": "pi-globe", "max_children": 0},
"texture" : {"icon": "pi-texture", "max_children": 0}, "texture" : {"icon": "pi-texture", "max_children": 0},
"video" : {"icon": "pi-film-thick", "max_children": 0}, "video" : {"icon": "pi-play", "max_children": 0},
"blog" : {"icon": "pi-newspaper", "max_children": 0}, "blog" : {"icon": "pi-newspaper", "max_children": 0},
"page" : {"icon": "pi-document-text", "max_children": 0}, "page" : {"icon": "pi-document-text", "max_children": 0},
"default" : {"icon": "pi-document"} "default" : {"icon": "pi-document"}
@@ -629,6 +673,10 @@ script.
/* UI Stuff */ /* UI Stuff */
$(window).on("load resize",function(){ $(window).on("load resize",function(){
containerResizeY($(window).height()); containerResizeY($(window).height());
if ($(window).width() > 480) {
project_container.style.height = (window.innerHeight - project_container.offsetTop) + "px";
}
}); });
{% if current_user_is_subscriber %} {% if current_user_is_subscriber %}

View File

@@ -1,23 +1,39 @@
| {% extends 'layout.html' %} | {% extends 'layout.html' %}
| {% from '_macros/_navigation.html' import navigation_homepage %}
| {% from '_macros/_opengraph.html' import opengraph %}
include ../../../pillar/src/templates/mixins/components
include mixins/components include mixins/components
| {# Default collection is 'Courses' #} | {# Default case is Open Projects #}
| {% set page_title = 'Courses' %} | {% set page_title = 'Open Projects' %}
| {% set page_description = 'In-depth training on character modeling, 3D printing, rigging, VFX and more.' %} | {% set page_description = 'Full production data and tutorials from all open movies, for you to use freely' %}
| {% set page_header_image = url_for('static', filename='assets/img/backgrounds/background_agent327_04.jpg', _external=True) %} | {% set page_header_image = url_for('static', filename='assets/img/backgrounds/background_agent327_01.jpg') %}
| {% set page_header_text = 'The iconic Blender Institute Open Movies. Featuring all the production files, assets, artwork, and never-seen-before content.' %}
| {% if title == 'workshops' %} | {% if title == 'courses' %}
| {% set page_title = 'Courses' %}
| {% set page_description = 'Production quality training by 3D professionals' %}
| {% set page_header_image = url_for('static', filename='assets/img/backgrounds/background_agent327_04.jpg') %}
| {% set page_header_text = 'Character modeling, 3D printing, VFX, rigging and more.' %}
| {% elif title == 'workshops' %}
| {% set page_title = 'Workshops' %} | {% set page_title = 'Workshops' %}
| {% set page_description = 'Enter the artist workshop and learn by example.' %} | {% set page_description = 'Production quality training by 3D professionals' %}
| {% set page_header_image = url_for('static', filename='assets/img/backgrounds/background_agent327_04.jpg', _external=True) %} | {% set page_header_image = url_for('static', filename='assets/img/backgrounds/background_agent327_04.jpg') %}
| {% set page_header_text = 'Enter the artist workshop and learn by example.' %}
| {% endif %} | {% endif %}
| {% block og %} | {% block og %}
| {{ opengraph(page_title, page_description, page_header_image, request.url) }} meta(property="og:type", content="website")
meta(property="og:url", content="{{ request.url }}")
meta(property="og:title", content="{{ page_title }} on Blender Cloud")
meta(name="twitter:title", content="{{ page_title }} on Blender Cloud")
meta(property="og:description", content="{{ page_description }}")
meta(name="twitter:description", content="{{ page_description }}")
meta(property="og:image", content="{{ page_header_image }}")
meta(name="twitter:image", content="{{ page_header_image }}")
| {% endblock %} | {% endblock %}
| {% block page_title %} | {% block page_title %}
@@ -25,28 +41,47 @@ include mixins/components
| {% endblock %} | {% endblock %}
| {% block navigation_tabs %} | {% block navigation_tabs %}
| {{ navigation_homepage(title) }}
| {% if title in ['courses', 'workshops'] %}
+nav-secondary
+nav-secondary-link(
class="{% if title == 'workshops' %}active{% endif %}",
href="{{ url_for('cloud.workshops') }}")
| Workshops
+nav-secondary-link(
class="{% if title == 'courses' %}active{% endif %}",
href="{{ url_for('cloud.courses') }}")
| Courses
+nav-secondary-link(
class="{% if title == 'gallery' %}active{% endif %}",
href="{{ url_for('projects.view', project_url='gallery') }}")
| Art Gallery
| {% endif %}
| {% endblock navigation_tabs %} | {% endblock navigation_tabs %}
| {% block body %} | {% block body %}
.container.py-4
+category_list_header('{{ page_title }}', '{{ page_description }}', '{{ request.url }}')
+card-deck() .container.pb-5
.pt-4
h2.text-uppercase.font-weight-bold
| {{ page_title }}
.lead
| {{ page_header_text }}
hr.pb-2
+card-deck(3)
| {% for project in projects %} | {% for project in projects %}
| {% if (project.status == 'published') or (project.status == 'pending' and current_user.is_authenticated) and project._id != config.MAIN_PROJECT_ID %} | {% if (project.status == 'published') or (project.status == 'pending' and current_user.is_authenticated) and project._id != config.MAIN_PROJECT_ID %}
+card( +card(data-url="{{ url_for('projects.view', project_url=project.url) }}", tabindex='{{ loop.index }}')
class='js-project-go card-fade cursor-pointer mb-4', | {% if project.picture_header %}
style="min-width: 30%", a(href="{{ url_for('projects.view', project_url=project.url) }}")
data-url="{{ url_for('projects.view', project_url=project.url) }}",
tabindex='{{ loop.index }}')
| {% if project.picture_16_9 %}
a.card-thumbnail(href="{{ url_for('projects.view', project_url=project.url) }}")
img.card-img-top( img.card-img-top(
alt="{{ project.name }}", src="{{ project.picture_header.thumbnail('l', api=api) }}", alt="{{ project.name }}")
src="{{ project.picture_16_9.thumbnail('l', api=api) }}")
| {% endif %} | {% endif %}
.card-body .card-body

View File

@@ -1,24 +1,19 @@
| {% extends 'layout.html' %} | {% extends 'layout.html' %}
| {% from '_macros/_navigation.html' import navigation_homepage %} | {% block page_title %}Services{% endblock %}
| {% from '_macros/_opengraph.html' import opengraph %} | {% set title = 'services' %}
include ../../../pillar/src/templates/mixins/components
include mixins/components include mixins/components
| {% set title = 'services' %}
| {% set page_title = 'Services' %}
| {% set page_description = 'On Blender Cloud you can create and share personal projects, access our texture and HDRI library (or create your own), keep track of your production, manage your renders and much more!' %}
| {% set page_header_image = url_for('static', filename='assets/img/backgrounds/background_services_16_9.jpg', _external=true) %}
| {% block page_title %}{{ page_title }}{% endblock %}
| {% block og %} | {% block og %}
| {{ opengraph(page_title, page_description, page_header_image, request.url) }} meta(property="og:type", content="website")
| {% endblock %} meta(property="og:url", content="{{ request.url }}")
| {% block navigation_tabs %} meta(property="og:title", content="Services - Blender Cloud")
| {{ navigation_homepage(title) }} meta(name="twitter:title", content="Services - Blender Cloud")
| {% endblock navigation_tabs %} meta(property="og:description", content="Personal Projects · Blender Integration · Texture Browsing · Production Management")
meta(name="twitter:description", content="Personal Projects · Blender Integration · Texture Browsing · Production Management")
meta(property="og:image", content="{{ url_for('static', filename='assets/img/backgrounds/background_services.jpg')}}")
meta(name="twitter:image", content="{{ url_for('static', filename='assets/img/backgrounds/background_services.jpg')}}")
| {% endblock %}
| {% block page_overlay %} | {% block page_overlay %}
#page-overlay.video #page-overlay.video
@@ -26,8 +21,8 @@ include mixins/components
| {% endblock %} | {% endblock %}
| {% block body %} | {% block body %}
.container.py-4 - var header_text = "On Blender Cloud you can create and share personal projects, access our texture and HDRI library (or create your own), keep track of your production, manage your renders and much more!";
+category_list_header('{{ page_title }}', '{{ page_description }}', '{{ request.url }}') +jumbotron("Services", header_text, "{{ url_for('static', filename='assets/img/backgrounds/services_projects.jpg')}}")
- var addon_text = 'Available through the <a href="{{ url_for(\'cloud.services\') }}#blender-cloud-add-on">Blender Cloud add-on</a>'; - var addon_text = 'Available through the <a href="{{ url_for(\'cloud.services\') }}#blender-cloud-add-on">Blender Cloud add-on</a>';
section#blender-cloud-add-on.page-card section#blender-cloud-add-on.page-card
@@ -45,18 +40,15 @@ section#blender-cloud-add-on.page-card
small Blender Cloud add-on requires Blender 2.78 or newer small Blender Cloud add-on requires Blender 2.78 or newer
a.btn.btn-primary( a.btn.btn-primary(
href="/r/downloads/blender_cloud-latest-addon.zip", href="/r/downloads/blender_cloud-latest-addon.zip")
title="Download Blender Cloud add-on")
i.pi-download i.pi-download
| Download add-on &nbsp;<small>v</small> {{ config.BLENDER_CLOUD_ADDON_VERSION }} | Download add-on &nbsp;<small>v</small> {{ config.BLENDER_CLOUD_ADDON_VERSION }}
a.page-card-side( .page-card-side
href="/r/downloads/blender_cloud-latest-addon.zip",
title="Download Blender Cloud add-on")
img( img(
src="{{ url_for('static', filename='assets/img/features/blender_cloud_addon_thumbnail.png')}}") src="{{ url_for('static', filename='assets/img/features/blender_cloud_addon_thumbnail.png')}}")
section#blender-sync.page-card section#blender-sync.page-card.right
.page-card-side .page-card-side
h2.page-card-title Blender Sync h2.page-card-title Blender Sync
.page-card-summary .page-card-summary
@@ -70,25 +62,21 @@ section#blender-sync.page-card
.tip !{addon_text} .tip !{addon_text}
a.btn.btn-outline-primary( a.btn.btn-outline-primary(
href="/r/downloads/blender_cloud-latest-addon.zip", href="/r/downloads/blender_cloud-latest-addon.zip")
title="Download Blender Cloud add-on")
i.pi-download i.pi-download
| Download add-on &nbsp;<small>v</small> {{ config.BLENDER_CLOUD_ADDON_VERSION }} | Download add-on &nbsp;<small>v</small> {{ config.BLENDER_CLOUD_ADDON_VERSION }}
a.btn.btn-link( a.btn.btn-link(
href="/blog/introducing-blender-sync", href="/blog/introducing-blender-sync")
title="Learn more about Blender Sync")
| Learn More | Learn More
i.pi-angle-right i.pi-angle-right
a.page-card-side( .page-card-side
href="/blog/introducing-blender-sync",
title="Learn more about Blender Sync")
img( img(
src="{{ url_for('static', filename='assets/img/features/sync_thumbnail.jpg')}}") src="{{ url_for('static', filename='assets/img/features/sync_thumbnail.jpg')}}")
section#texture-browser.page-card section#texture-browser.page-card.right
.page-card-side .page-card-side
h2.page-card-title Texture & HDRI Browser h2.page-card-title Texture & HDRI Browser
.page-card-summary .page-card-summary
@@ -101,21 +89,16 @@ section#texture-browser.page-card
a.btn.btn-outline-primary.js-watch-video( a.btn.btn-outline-primary.js-watch-video(
href="https://www.youtube.com/watch?v=-srXYv2Osjw", href="https://www.youtube.com/watch?v=-srXYv2Osjw",
data-youtube-id="-srXYv2Osjw", data-youtube-id="-srXYv2Osjw")
title="Watch video about Texture and HDRI Browser")
i.pi-play i.pi-play
| Watch Video | Watch Video
a.page-card-side( .page-card-side
class="js-watch-video",
href="https://www.youtube.com/watch?v=-srXYv2Osjw",
data-youtube-id="-srXYv2Osjw",
title="Watch video about Texture and HDRI Browser")
img( img(
src="{{ url_for('static', filename='assets/img/features/tex_library_thumbnail.jpg')}}") src="{{ url_for('static', filename='assets/img/features/tex_library_thumbnail.jpg')}}")
section#image-sharing.page-card section#image-sharing.page-card.right
.page-card-side .page-card-side
h2.page-card-title Image Sharing h2.page-card-title Image Sharing
.page-card-summary .page-card-summary
@@ -126,27 +109,21 @@ section#image-sharing.page-card
a.btn.btn-outline-primary.js-watch-video( a.btn.btn-outline-primary.js-watch-video(
href="https://www.youtube.com/watch?v=yvtqeMBOAyk", href="https://www.youtube.com/watch?v=yvtqeMBOAyk",
data-youtube-id="yvtqeMBOAyk", data-youtube-id="yvtqeMBOAyk")
title="Watch video about Image Sharing")
i.pi-play i.pi-play
| Watch Video | Watch Video
a.btn.btn-link( a.btn.btn-link(
href="/blog/introducing-image-sharing", href="/blog/introducing-image-sharing")
title="Learn more about Image Sharing")
| Learn More | Learn More
i.pi-angle-right i.pi-angle-right
a.page-card-side( .page-card-side
class="js-watch-video",
href="https://www.youtube.com/watch?v=yvtqeMBOAyk",
data-youtube-id="yvtqeMBOAyk",
title="Watch video about Image Sharing")
img( img(
src="{{ url_for('static', filename='assets/img/features/image_sharing_thumbnail.jpg')}}") src="{{ url_for('static', filename='assets/img/features/image_sharing_thumbnail.jpg')}}")
section#projects.page-card section#projects.page-card.right
.page-card-side .page-card-side
h2.page-card-title Private Projects h2.page-card-title Private Projects
.page-card-summary. .page-card-summary.
@@ -154,19 +131,16 @@ section#projects.page-card
Upload assets and collaborate with other Blender Cloud members. Upload assets and collaborate with other Blender Cloud members.
a.btn.btn-link( a.btn.btn-link(
href="/blog/introducing-private-projects", href="/blog/introducing-private-projects")
title="Learn more about Private Projects")
| Learn More | Learn More
i.pi-angle-right i.pi-angle-right
a.page-card-side( .page-card-side
href="/blog/introducing-private-projects",
title="Learn more about Private Projects")
img( img(
src="{{ url_for('static', filename='assets/img/features/projects_thumbnail.jpg')}}") src="{{ url_for('static', filename='assets/img/features/projects_thumbnail.jpg')}}")
section#attract.page-card section#attract.page-card.right
.page-card-side .page-card-side
h2.page-card-title h2.page-card-title
| Attract | Attract
@@ -185,14 +159,12 @@ section#attract.page-card
| Learn More | Learn More
i.pi-angle-right i.pi-angle-right
a.page-card-side( .page-card-side
href="/blog/attract-and-flamenco-public-beta",
title="Learn more about Attract")
img( img(
src="{{ url_for('static', filename='assets/img/features/attract_thumbnail.jpg')}}") src="{{ url_for('static', filename='assets/img/features/attract_thumbnail.jpg')}}")
section#flamenco.page-card section#flamenco.page-card.right
.page-card-side .page-card-side
h2.page-card-title h2.page-card-title
| Flamenco | Flamenco
@@ -211,9 +183,7 @@ section#flamenco.page-card
| Learn More | Learn More
i.pi-angle-right i.pi-angle-right
a.page-card-side( .page-card-side
href="https://flamenco.io",
title="Learn more about Flamenco")
img( img(
src="{{ url_for('static', filename='assets/img/features/flamenco_thumbnail.jpg')}}") src="{{ url_for('static', filename='assets/img/features/flamenco_thumbnail.jpg')}}")

View File

@@ -19,8 +19,8 @@ style.
and the Blender Cloud. If you do not wish to be bound by this Agreement, do not use the and the Blender Cloud. If you do not wish to be bound by this Agreement, do not use the
Blender Cloud Service. Blender Cloud Service.
p. p.
Blender Cloud is an activity of Blender Institute B.V. - Buikslotermeerplein 161 - Blender Cloud is an activity of Blender Institute B.V. - Entrepotdok 57A - 1018 AD Amsterdam
1025 ET Amsterdam - the Netherlands, contact: institute@blender.org. - the Netherlands, contact: institute@blender.org.
p. p.
Blender Institute has been authorised by Stichting Blender Foundation to conduct these Blender Institute has been authorised by Stichting Blender Foundation to conduct these
services on blender.org. Blender Institute is committed to comply to and support the goals of services on blender.org. Blender Institute is committed to comply to and support the goals of
@@ -57,10 +57,10 @@ style.
h2 Blender Cloud Membership fee h2 Blender Cloud Membership fee
p. p.
To register and activate a membership an additional charge will apply, including a minimum of To register and activate a membership an additional charge will apply, including a minimum of
1 month of membership fees. Fees are: (Apr 09, 2020) 3 months of membership fees. Fees are: (Feb 23, 2014)
p. p.
9.90 EUR/month for subscription with automatic renewal and 14.90 EUR/month for subscriptions 45 euro (59 USD), membership registration, which includes 3 months Cloud membership.
with manual renewal. After that, 10 euro (13.50 USD), monthly membership fee
h2 Refund policy h2 Refund policy
p. p.

View File

@@ -12,7 +12,7 @@ style(type='text/css').
//-------------------------------------------------------------------------------------------------- //--------------------------------------------------------------------------------------------------
| {% if user_cls == 'demo' %} | {% if user_cls == 'demo' %}
h4.text-info.py-3 h3.subscription-demo
i.pi-heart-filled i.pi-heart-filled
| You have a free account | You have a free account
hr hr
@@ -23,7 +23,7 @@ p.
//-------------------------------------------------------------------------------------------------- //--------------------------------------------------------------------------------------------------
| {% elif user_cls == 'outsider' %} | {% elif user_cls == 'outsider' %}
h4.text-info.py-3 h3.subscription-missing
i.pi-info i.pi-info
| You do not have an active subscription. | You do not have an active subscription.
hr hr
@@ -33,23 +33,23 @@ h3
//-------------------------------------------------------------------------------------------------- //--------------------------------------------------------------------------------------------------
| {% elif user_cls == 'subscriber-expired' %} | {% elif user_cls == 'subscriber-expired' %}
| {% set renew_url = url_for('cloud.renew_subscription') %} | {% set renew_url = url_for('cloud.renew_subscription') %}
h4.text-info.py-3 h3.subscription-missing
i.pi-info i.pi-info
a(href="{{renew_url}}") Your subscription can be renewed a(href="{{renew_url}}") Your subscription can be renewed
hr hr
p.text-danger Subscription expired on: <strong>{{ expiration_date }}</strong> p.text-danger Subscription expired on: <strong>{{ expiration_date }}</strong>
p p
a.btn.btn-success.px-5(href="{{renew_url}}") Renew now a.btn.btn-success(href="{{renew_url}}") Renew now
//-------------------------------------------------------------------------------------------------- //--------------------------------------------------------------------------------------------------
| {% elif current_user.has_cap('subscriber') %} | {% elif current_user.has_cap('subscriber') %}
h4.text-success.py-3 h3.subscription-active
i.pi-heart-filled.text-danger.pr-2 i.pi-check
| Your subscription is active | Your subscription is active
//--------------------------------- //---------------------------------
| {% if user_cls == 'subscriber' %} | {% if user_cls == 'subscriber' %}
h5 Thank you for supporting Blender! h4 Thank you for supporting us!
hr hr
p Subscription expires on: <strong>{{ expiration_date }}</strong> p Subscription expires on: <strong>{{ expiration_date }}</strong>
p p
@@ -64,9 +64,10 @@ p Your organisation provides you with your subscription.
| {% endif %} | {% endif %}
hr hr
p
button#recheck_subscription.btn.btn-outline-secondary(onclick="javascript:recheck_subscription(this)") Re-check my subscription
button#recheck_subscription.btn.btn-sm.btn-outline-secondary.px-5(onclick="javascript:recheck_subscription(this)") hr
| Re-check my Subscription
script. script.
function recheck_subscription(button) { function recheck_subscription(button) {

View File

@@ -10,9 +10,8 @@
{{ subfield.label }} {{ subfield.label }}
| {% endfor %} | {% endfor %}
.py-3 .buttons
button.btn.btn-outline-success.px-5.button-submit(type='submit') button.btn.btn-outline-success.button-submit(type='submit')
i.pi-check.pr-2 i.pi-check
| {{ _("Save Changes") }} | Save Changes
| {% endblock %} | {% endblock %}

View File

@@ -1,16 +1,14 @@
| {% extends 'users/settings/base.html' %} | {% extends 'users/settings/base.html' %}
include ../../../../../pillar/src/templates/mixins/components
| {% block settings_sidebar_menu %} | {% block settings_sidebar_menu %}
| {{ super() }} | {{ super() }}
+nav-secondary-link( a(class="{% if title == 'emails' %}active{% endif %}",
class="{% if title == 'emails' %}active{% endif %}",
href="{{ url_for('settings.emails') }}") href="{{ url_for('settings.emails') }}")
i.pr-3.pi-email li
span Emails i.pi-email
+nav-secondary-link( | Emails
class="{% if title == 'billing' %}active{% endif %}", a(class="{% if title == 'billing' %}active{% endif %}",
href="{{ url_for('settings.billing') }}") href="{{ url_for('settings.billing') }}")
i.pr-3.pi-credit-card li
span Subscription i.pi-credit-card
| Subscription
| {% endblock %} | {% endblock %}

View File

@@ -1,6 +1,4 @@
| {% extends 'layout.html' %} | {% extends 'layout.html' %}
include ../../../pillar/src/templates/mixins/components
| {% block page_title %}Welcome{% endblock %} | {% block page_title %}Welcome{% endblock %}
| {% set title = 'join' %} | {% set title = 'join' %}
@@ -21,123 +19,65 @@ meta(property="og:image", content="{{ url_for('static', filename='assets/img/bac
| {% block navigation_search %}{% endblock %} | {% block navigation_search %}{% endblock %}
| {% block navigation_sections %} | {% block navigation_sections %}
+nav-secondary-link(href="#pricing") li
a.navbar-item(href="{{ url_for('main.main_blog') }}")
span Blog
li
a.navbar-item(href="#pricing")
span Pricing span Pricing
| {% endblock navigation_sections %} | {% endblock navigation_sections %}
| {% block navigation_user %} | {% block navigation_user %}
li.pr-1 li.pt-1.pr-1
| {% if current_user.is_anonymous %} | {% if current_user.is_anonymous %}
a.btn.btn-sm.btn-outline-primary.px-3(href="{{ url_for('users.login', next='/') }}") a.btn.btn-outline-success(href="{{ url_for('users.login', next='/') }}")
| Log in &amp; Browse | Log in and Explore
| {% else %} | {% else %}
a.btn.btn-sm.btn-outline-primary.px-3(href="{{ url_for('main.homepage') }}") a.btn.btn-outline-success(href="{{ url_for('main.homepage') }}")
| Browse | Explore
| {% endif %} | {% endif %}
| {% endblock navigation_user %} | {% endblock navigation_user %}
| {% block body %} | {% block body %}
#page-container.join #page-container.join
#page-header( #page-header(
style="background-image: url({{ url_for('static', filename='assets/img/backgrounds/background_spring_02.jpg')}})") style="background-image: url({{ url_for('static', filename='assets/img/backgrounds/background_dweebs_01.jpg')}})")
.container.wide-on-sm .container.wide-on-sm
.row .row
.col-sm-8.col-md-6.col-lg-6 .col-sm-8.col-md-6.col-lg-6
.page-title .page-title
| Blender Cloud | Open content #[br] creation platform
.page-title-summary. .page-title-summary.
Join the production platform as used daily by a world-class Support a world-class team of artists and developers,
team of artists and developers. access training, assets and use the same animation
production pipeline used to make shorts like Agent 327 or Cosmos Laundromat
for your own projects.
#page-content #page-content
section.page-card.intro
.page-card-side
h2.page-card-title A Different Kind of Cloud
.page-card-summary
p.
Blender Cloud is a fresh take on computer animation. We are an international group
of artists and developers #[strong creating short films with Blender].
We share what we make - assets, trainings and the finished films - as Open Content.
p.
With every project, we push Blender beyond its limits and contribute to
improve it for everyone's benefit.
.page-card-side
a.page-card-image(href="/p/caminandes-3/56bdacccc379cf00797160b0", target="_blank")
video(autoplay, loop)
source(src="{{ url_for('static', filename='assets/img/features/animation_review_01.mp4')}}")
section.page-card-header section.page-card-header
a(href="{{ url_for('cloud.courses') }}") a(href="{{ subscribe_url }}")
h2 Featured Content h2 Subscribe to Get
.page-triplet-container.homepage
.row
.col-md-4
.triplet-card(data-url="/p/spring/")
.triplet-card-thumbnail
img(
alt="Spring Open Movie",
src="{{ url_for('static', filename='assets/img/features/open_movies_spring_02.jpg')}}")
.triplet-card-info
h3 Spring Open Movie
p.
Explore the production assets of the latest Blender Animation Studio short film.
a.triplet-cta(href="/p/spring/")
| LEARN MORE
.col-md-4
.triplet-card(data-url="/p/speed-sculpting/")
.triplet-card-thumbnail
img(
alt="Speed Sculpting Workshop",
src="{{ url_for('static', filename='assets/img/features/training_speed_sculpting.jpg')}}")
.triplet-card-info
h3 Speed Sculpting
p.
Learn speed sculpting in Blender 2.8 with Julien Kaspar.
a.triplet-cta(href="/p/speed-sculpting/")
| LEARN MORE
.col-md-4
.triplet-card(data-url="/p/grease-pencil-fundamentals/")
.triplet-card-thumbnail
img(
alt="Grease Pencil Fundamentals",
src="{{ url_for('static', filename='assets/img/features/training_grease_pencil.jpg')}}")
.triplet-card-info
h3 Grease Pencil
p.
Become proficient with the latest Blender 2.8 Grease Pencil.
a.triplet-cta(href="/p/grease-pencil-fundamentals/")
| LEARN MORE
.row.training-other
.col-md-10.col-md-offset-1
p.
Other training:
#[a(href="/p/toon-character-workflow/") Toon Character Workflow],
#[a(href="/p/3d-printing/") Blender for 3D Printing],
#[a(href="/p/game-asset-creation/") Game Asset Creation],
#[a(href="/p/blenderella/") Character Modeling],
#[a(href="/p/character-animation/") Character Animation],
#[a(href="/p/humane-rigging/") Introduction] and
#[a(href="/p/blenrig/") Advanced Rigging],
#[a(href="/p/track-match-2/") VFX Workflow],
#[a(href="/p/creature-factory-2/") Creature] and
#[a(href="/p/venoms-lab-2/") Cartoon Character creation],
#[a(href="/p/chaos-evolution/") Advanced]
#[a(href="/p/blend-and-paint/") Digital Painting] and
#[a(href="{{ url_for('cloud.courses') }}") much more]!
//section.page-card.intro
//
// .page-card-side
// h2.page-card-title A Different Kind of Cloud
// .page-card-summary
// p.
// Blender Cloud is a fresh take on computer animation. We are an international group
// of artists and developers #[strong creating short films with Blender].
// We share what we make - assets, trainings and the finished films - as Open Content.
// p.
// With every project, we push Blender beyond its limits and contribute to
// improve it for everyone's benefit.
//
// .page-card-side
// a.page-card-image(href="/p/caminandes-3/56bdacccc379cf00797160b0", target="_blank")
// video(autoplay, loop)
// source(src="{{ url_for('static', filename='assets/img/features/animation_review_01.mp4')}}")
//
//
//section.page-card-header
// a(href="{{ subscribe_url }}")
// h2 Subscribe to Get
section.page-card.training.right.light( section.page-card.training.right.light(
@@ -174,6 +114,69 @@ li.pr-1
src="{{ url_for('static', filename='assets/img/backgrounds/background_sybren_01.jpg')}}") src="{{ url_for('static', filename='assets/img/backgrounds/background_sybren_01.jpg')}}")
section.page-card-header
a(href="{{ url_for('cloud.courses') }}")
h2 Featured Content
.page-triplet-container.homepage
.row
.col-md-4
.triplet-card(data-url="/p/minecraft-animation-workshop/")
.triplet-card-thumbnail
img(
alt="Textures",
src="{{ url_for('static', filename='assets/img/features/training_minecraft_animation.jpg')}}")
.triplet-card-info
h3 Minecraft Animation
p.
Learn how to make animations with this workshop by Dillon Gu.
a.triplet-cta(href="/p/minecraft-animation-workshop/")
| LEARN MORE
.col-md-4
.triplet-card(data-url="/p/motion-graphics/")
.triplet-card-thumbnail
img(
alt="HDRI",
src="{{ url_for('static', filename='assets/img/features/training_motion_graphics.jpg')}}")
.triplet-card-info
h3 Motion Graphics
p.
A comprehensive guide to motion graphics techniques using Blender.
a.triplet-cta(href="/p/motion-graphics/")
| LEARN MORE
.col-md-4
.triplet-card(data-url="/p/gallery")
.triplet-card-thumbnail
img(
alt="Characters",
src="{{ url_for('static', filename='assets/img/features/training_bob_forest.jpg')}}")
.triplet-card-info
h3 Art Walk-throughs
p.
Follow the creative process and techniques behind stunning artwork.
a.triplet-cta(href="/p/gallery")
| LEARN MORE
.row.training-other
.col-md-10.col-md-offset-1
p.
Other training:
#[a(href="/p/toon-character-workflow/") Toon Character Workflow],
#[a(href="/p/3d-printing/") Blender for 3D Printing],
#[a(href="/p/game-asset-creation/") Game Asset Creation],
#[a(href="/p/blenderella/") Character Modeling],
#[a(href="/p/character-animation/") Character Animation],
#[a(href="/p/humane-rigging/") Introduction] and
#[a(href="/p/blenrig/") Advanced Rigging],
#[a(href="/p/track-match-2/") VFX Workflow],
#[a(href="/p/creature-factory-2/") Creature] and
#[a(href="/p/venoms-lab-2/") Cartoon Character creation],
#[a(href="/p/chaos-evolution/") Advanced]
#[a(href="/p/blend-and-paint/") Digital Painting] and
#[a(href="{{ url_for('cloud.courses') }}") much more]!
section.page-card.open-movies.light( section.page-card.open-movies.light(
style="background-image: url({{ url_for('static', filename='assets/img/backgrounds/background_blue_01.jpg')}})") style="background-image: url({{ url_for('static', filename='assets/img/backgrounds/background_blue_01.jpg')}})")

Binary file not shown.

Before

Width:  |  Height:  |  Size: 116 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 601 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 138 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 139 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 129 KiB

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