Compare commits
2 Commits
production
...
flamencoRe
Author | SHA1 | Date | |
---|---|---|---|
b165c11cc3 | |||
22bd1a1a04 |
1
.gitignore
vendored
@@ -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
|
||||||
|
30
README.md
@@ -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.
|
||||||
|
@@ -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
|
||||||
|
|
||||||
@@ -44,8 +42,7 @@ class CloudExtension(PillarExtension):
|
|||||||
'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', 'modeling', 'rigging', 'sculpting', 'shading', 'texturing', 'lighting',
|
||||||
'character-pipeline', 'effects', 'video-editing', 'digital-painting', 'production-design',
|
'character-pipeline', 'effects', 'video-editing'],
|
||||||
'walk-through'],
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def eve_settings(self):
|
def eve_settings(self):
|
||||||
@@ -89,54 +86,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
|
||||||
|
|
||||||
|
10
cloud/cli.py
@@ -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)
|
||||||
|
@@ -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')
|
|
335
cloud/routes.py
@@ -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)
|
||||||
|
|
||||||
@@ -444,27 +423,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 +458,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):
|
||||||
|
@@ -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)
|
|
@@ -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)
|
|
||||||
|
@@ -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',
|
|
||||||
}
|
|
||||||
|
@@ -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 "==================================================================="
|
||||||
|
@@ -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"
|
||||||
|
@@ -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.
|
|
@@ -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
@@ -0,0 +1 @@
|
|||||||
|
build-quick.sh
|
@@ -1 +0,0 @@
|
|||||||
build-quick.sh
|
|
@@ -1 +0,0 @@
|
|||||||
build-all.sh
|
|
34
deploy/build-quick.sh
Executable 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."
|
||||||
|
|
@@ -1,9 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
set -e
|
|
||||||
|
|
||||||
NAME="$(basename "$0")"
|
|
||||||
|
|
||||||
./2docker.sh
|
|
||||||
./${NAME/full-/build-}
|
|
||||||
./2server.sh cloud2
|
|
@@ -1 +0,0 @@
|
|||||||
full-all.sh
|
|
@@ -1 +0,0 @@
|
|||||||
full-all.sh
|
|
@@ -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."
|
|
||||||
|
@@ -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]; \
|
||||||
|
@@ -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>
|
||||||
|
@@ -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
|
||||||
|
|
||||||
|
@@ -1,2 +0,0 @@
|
|||||||
RemoteIPHeader X-Forwarded-For
|
|
||||||
RemoteIPInternalProxy 172.16.0.0/12
|
|
@@ -106,6 +106,19 @@ 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)
|
||||||
|
},
|
||||||
|
# 'flamenco-resume-job-archiving': {
|
||||||
|
# 'task': 'flamenco.celery.job_archival.resume_job_archiving',
|
||||||
|
# 'schedule': 3600, # every N seconds
|
||||||
|
# },
|
||||||
|
}
|
||||||
|
|
||||||
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/'
|
||||||
|
|
||||||
|
@@ -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:
|
||||||
|
@@ -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
|
|
@@ -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
@@ -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
|
||||||
|
|
||||||
|
96
gulpfile.js
@@ -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,19 @@ 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/'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* 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,26 +45,27 @@ 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()));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
@@ -71,7 +73,7 @@ gulp.task('templates', function(done) {
|
|||||||
|
|
||||||
|
|
||||||
/* 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 +81,29 @@ 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()));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
// 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',
|
gulp.watch('src/scripts/*.js',['scripts']);
|
||||||
];
|
gulp.watch('src/templates/**/*.pug',['templates']);
|
||||||
|
|
||||||
let watchTemplates = [
|
|
||||||
'src/templates/**/*.pug',
|
|
||||||
source.pillar + 'src/templates/**/*.pug',
|
|
||||||
];
|
|
||||||
|
|
||||||
gulp.watch(watchStyles, gulp.series('styles'));
|
|
||||||
gulp.watch(watchScripts, gulp.series('scripts'));
|
|
||||||
gulp.watch(watchTemplates, gulp.series('templates'));
|
|
||||||
done();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
// Erases all generated files in output directories.
|
// 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 +111,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']));
|
||||||
|
@@ -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
|
|
3808
package-lock.json
generated
@@ -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,7 +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"
|
"video.js": "^7.2.2"
|
||||||
}
|
}
|
||||||
|
1917
poetry.lock
generated
@@ -1,2 +0,0 @@
|
|||||||
[virtualenvs]
|
|
||||||
in-project = false
|
|
@@ -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
@@ -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
@@ -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
@@ -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,
|
||||||
|
)
|
@@ -2,27 +2,143 @@
|
|||||||
* 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 an asset card, returns a jQuery object. */
|
||||||
$.get('/api/nodes/tagged/' + $card_deck_element.data('assetTag'))
|
function renderAsset(node) {
|
||||||
|
let card = $('<a class="card asset card-image-fade pr-0 mx-0 mb-2">')
|
||||||
|
.addClass('js-tagged-asset')
|
||||||
|
.attr('href', '/nodes/' + node._id + '/redir')
|
||||||
|
.attr('title', node.name);
|
||||||
|
|
||||||
|
let thumbnail_container = $('<div class="embed-responsive embed-responsive-16by9">');
|
||||||
|
|
||||||
|
function warnNoPicture() {
|
||||||
|
let card_icon = $('<div class="card-img-top card-icon embed-responsive-item">');
|
||||||
|
card_icon.html('<i class="pi-' + node.node_type + '">');
|
||||||
|
thumbnail_container.append(card_icon);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!node.picture) {
|
||||||
|
warnNoPicture();
|
||||||
|
} else {
|
||||||
|
// 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
|
console.log(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 class="card-img-top embed-responsive-item">')
|
||||||
|
.attr('alt', node.name)
|
||||||
|
.attr('src', variation.link)
|
||||||
|
.attr('width', variation.width)
|
||||||
|
.attr('height', variation.height);
|
||||||
|
thumbnail_container.append(img);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
card.append(thumbnail_container);
|
||||||
|
|
||||||
|
/* Card body for title and meta info. */
|
||||||
|
let card_body = $('<div class="card-body py-2 d-flex flex-column">');
|
||||||
|
let card_title = $('<div class="card-title mb-1 font-weight-bold">');
|
||||||
|
card_title.text(node.name);
|
||||||
|
card_body.append(card_title);
|
||||||
|
|
||||||
|
let card_meta = $('<ul class="card-text list-unstyled d-flex text-black-50 mt-auto">');
|
||||||
|
card_meta.append('<li>' + node._created + '</li>');
|
||||||
|
card_body.append(card_meta);
|
||||||
|
|
||||||
|
/* Video progress and 'watched' label. */
|
||||||
|
if (node.view_progress){
|
||||||
|
let card_progress = $('<div class="progress rounded-0">');
|
||||||
|
let card_progress_bar = $('<div class="progress-bar">');
|
||||||
|
card_progress_bar.css('width', node.view_progress.progress_in_percent);
|
||||||
|
card_progress.append(card_progress_bar);
|
||||||
|
card_body.append(card_progress);
|
||||||
|
|
||||||
|
if (node.view_progress.done){
|
||||||
|
let card_progress_done = $('<div class="card-label">WATCHED</div>');
|
||||||
|
card_body.append(card_progress_done);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 'Free' ribbon for public assets. */
|
||||||
|
if (node.permissions && node.permissions.world){
|
||||||
|
card.addClass('free');
|
||||||
|
}
|
||||||
|
|
||||||
|
card.append(card_body);
|
||||||
|
|
||||||
|
return card;
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadNext(card_deck_element) {
|
||||||
|
let $card_deck = $(card_deck_element);
|
||||||
|
let tagged_assets = card_deck_element.tagged_assets; // Stored here by loadTaggedAssets().
|
||||||
|
let already_loaded = $card_deck.find('a.js-tagged-asset').length;
|
||||||
|
|
||||||
|
let load_next = $card_deck.find('a.js-load-next');
|
||||||
|
|
||||||
|
let nodes_to_load = tagged_assets.slice(already_loaded, already_loaded + LOAD_NEXT_COUNT);
|
||||||
|
for (node of nodes_to_load) {
|
||||||
|
let link = renderAsset(node);
|
||||||
|
load_next.before(link);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (already_loaded + LOAD_NEXT_COUNT >= tagged_assets.length)
|
||||||
|
load_next.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
$.fn.loadTaggedAssets = function(LOAD_INITIAL_COUNT, LOAD_NEXT_COUNT) {
|
||||||
|
this.each(function(index, card_deck_element) {
|
||||||
|
// TODO(Sybren): show a 'loading' animation.
|
||||||
|
$.get('/api/nodes/tagged/' + card_deck_element.dataset.assetTag)
|
||||||
|
.fail(function(error) {
|
||||||
|
let msg = xhrErrorResponseMessage(error);
|
||||||
|
$('<a>').addClass('bg-danger').text(msg).appendTo(card_deck_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 card_deck_element so that we can later render more.
|
||||||
pillar.templates.Nodes.createListOf$nodeItems(resp, load_initial_count, load_next_count)
|
card_deck_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(card_deck_element);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't bother with a 'load next' link if there is no more.
|
||||||
|
if (resp.length <= LOAD_INITIAL_COUNT) return;
|
||||||
|
|
||||||
|
if (LOAD_NEXT_COUNT > 0) {
|
||||||
|
// Construct the 'load next' link.
|
||||||
|
let link = $('<a class="btn btn-outline-primary px-5 mb-auto mx-3 btn-block">')
|
||||||
|
.addClass('js-load-next')
|
||||||
|
.attr('href', 'javascript:void(0);')
|
||||||
|
.click(function() { loadNext(card_deck_element); return false; })
|
||||||
|
.text('Load More');
|
||||||
|
link.appendTo(card_deck_element);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@@ -1,60 +1,11 @@
|
|||||||
.random-featured
|
body.homepage
|
||||||
// Hide irrelevant info from cards.
|
.blog
|
||||||
li
|
.jumbotron
|
||||||
&.item-type,
|
padding-top: 6em
|
||||||
&.item-date
|
padding-bottom: 6em
|
||||||
@extend .d-none
|
|
||||||
|
|
||||||
// Fit 3 cards per row.
|
*
|
||||||
&.card-deck.card-deck-responsive
|
font-size: $h1-font-size
|
||||||
.card
|
|
||||||
+media-md
|
|
||||||
flex: 1 0 30%
|
|
||||||
max-width: 30%
|
|
||||||
flex: 1 0 31%
|
|
||||||
max-width: 31%
|
|
||||||
|
|
||||||
.homepage
|
.lead
|
||||||
.timeline
|
font-size: $font-size-base
|
||||||
.card-text,
|
|
||||||
.card-title
|
|
||||||
margin-bottom: 0 !important
|
|
||||||
padding: 0 !important
|
|
||||||
|
|
||||||
// Hide project name, it's already in the timeline.
|
|
||||||
a
|
|
||||||
display: none
|
|
||||||
|
|
||||||
// On blog posts, center text and title.
|
|
||||||
.h1.text-uppercase
|
|
||||||
+media-md
|
|
||||||
text-align: center
|
|
||||||
+media-xl
|
|
||||||
text-align: left
|
|
||||||
|
|
||||||
.node-details-description
|
|
||||||
+media-md
|
|
||||||
margin-left: auto
|
|
||||||
margin-right: auto
|
|
||||||
+media-xl
|
|
||||||
margin-left: 0
|
|
||||||
margin-right: auto
|
|
||||||
|
|
||||||
.featured-projects
|
|
||||||
+media-sm
|
|
||||||
padding-left: $spacer
|
|
||||||
padding-right: $spacer
|
|
||||||
|
|
||||||
.featured-project-card
|
|
||||||
+media-xl
|
|
||||||
.card-thumbnail
|
|
||||||
height: 100%
|
|
||||||
border-radius: $border-radius
|
|
||||||
|
|
||||||
.card-body
|
|
||||||
padding-left: 15px !important
|
|
||||||
padding-top: 0 !important
|
|
||||||
|
|
||||||
.homepage
|
|
||||||
.title-underline
|
|
||||||
padding-bottom: 2px
|
|
||||||
|
@@ -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)
|
|
@@ -1,36 +1,141 @@
|
|||||||
.landing-home
|
$node-latest-thumbnail-size: 160px
|
||||||
.page-content
|
$node-latest-gallery-thumbnail-size: 200px
|
||||||
@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
|
|
||||||
|
|
||||||
|
.landing
|
||||||
.node-details-description
|
.node-details-description
|
||||||
@extend .mx-auto
|
iframe
|
||||||
color: #ddd
|
max-width: 100%
|
||||||
font-size: 1.3em
|
|
||||||
|
|
||||||
a
|
.node-extra
|
||||||
color: $color-primary
|
display: flex
|
||||||
|
flex-direction: column
|
||||||
|
width: 100%
|
||||||
|
|
||||||
.btn-outline-primary
|
.node-updates
|
||||||
border-color: $color-text-light
|
flex: 1
|
||||||
color: $color-text-light
|
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
|
&:hover
|
||||||
border-color: transparent
|
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
|
||||||
|
|
||||||
|
.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)
|
||||||
|
|
||||||
|
|
||||||
.gallery
|
.gallery
|
||||||
max-width: 1024px
|
max-width: 1024px
|
||||||
@@ -42,10 +147,9 @@
|
|||||||
padding-bottom: 23%
|
padding-bottom: 23%
|
||||||
margin: 0.83%
|
margin: 0.83%
|
||||||
overflow: hidden
|
overflow: hidden
|
||||||
transition: box-shadow 150ms ease-in-out
|
|
||||||
|
|
||||||
&:hover
|
&:hover
|
||||||
box-shadow: 2px 6px 50px 0 rgba(black, .2)
|
box-shadow: 2px 2px 50px 0 rgba(0,0,0,0.3)
|
||||||
|
|
||||||
.img-container
|
.img-container
|
||||||
position: absolute
|
position: absolute
|
||||||
@@ -56,12 +160,45 @@
|
|||||||
width: 300%
|
width: 300%
|
||||||
transform: translate(-20%,-10%)
|
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)
|
@media screen and (max-width: 992px)
|
||||||
.thumbnail
|
.thumbnail
|
||||||
width: 22%
|
width: 22%
|
||||||
padding-bottom: 22%
|
padding-bottom: 22%
|
||||||
margin: 1.5%
|
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)
|
@media screen and (max-width: 720px)
|
||||||
.thumbnail
|
.thumbnail
|
||||||
width: 29%
|
width: 29%
|
||||||
@@ -73,13 +210,3 @@
|
|||||||
width: 44%
|
width: 44%
|
||||||
padding-bottom: 44%
|
padding-bottom: 44%
|
||||||
margin: 3%
|
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
|
|
||||||
|
@@ -1,4 +0,0 @@
|
|||||||
.list-first-new
|
|
||||||
li:first-child
|
|
||||||
span
|
|
||||||
@extend .new
|
|
@@ -1,5 +0,0 @@
|
|||||||
$color-bg-dark-pages: #151515
|
|
||||||
|
|
||||||
// Alias for Blender Cloud logo used in project edit.
|
|
||||||
.pi-cloud
|
|
||||||
@extend .pi-blender-cloud
|
|
@@ -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
|
||||||
|
@@ -7,9 +7,6 @@
|
|||||||
@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 "../../../pillar/node_modules/bootstrap/scss/root"
|
||||||
@import "../../../pillar/node_modules/bootstrap/scss/reboot"
|
@import "../../../pillar/node_modules/bootstrap/scss/reboot"
|
||||||
@@ -74,8 +71,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 +81,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 +96,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
|
|
||||||
|
@@ -7,9 +7,6 @@
|
|||||||
@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 "../../../pillar/node_modules/bootstrap/scss/root"
|
||||||
@import "../../../pillar/node_modules/bootstrap/scss/reboot"
|
@import "../../../pillar/node_modules/bootstrap/scss/reboot"
|
||||||
@@ -61,8 +58,6 @@ $pillar-font-path: "../../../../static/pillar/assets/font"
|
|||||||
@import "../../../pillar/src/styles/components/shortcode"
|
@import "../../../pillar/src/styles/components/shortcode"
|
||||||
@import "../../../pillar/src/styles/components/statusbar"
|
@import "../../../pillar/src/styles/components/statusbar"
|
||||||
@import "../../../pillar/src/styles/components/search"
|
@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/flyout"
|
||||||
@import "../../../pillar/src/styles/components/forms"
|
@import "../../../pillar/src/styles/components/forms"
|
||||||
@@ -73,7 +68,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/breadcrumbs"
|
|
||||||
|
|
||||||
@import "../../../pillar/src/styles/_notifications"
|
@import "../../../pillar/src/styles/_notifications"
|
||||||
@import "../../../pillar/src/styles/_comments"
|
@import "../../../pillar/src/styles/_comments"
|
||||||
@@ -89,5 +83,4 @@ $pillar-font-path: "../../../../static/pillar/assets/font"
|
|||||||
@import "../../../pillar/src/styles/plugins/_js_select2"
|
@import "../../../pillar/src/styles/plugins/_js_select2"
|
||||||
|
|
||||||
// Cloud components.
|
// Cloud components.
|
||||||
@import variables
|
|
||||||
@import "_project-landing"
|
@import "_project-landing"
|
||||||
|
@@ -13,8 +13,8 @@
|
|||||||
powered by Free and Open Source Software.
|
powered by Free and Open Source Software.
|
||||||
|
|
||||||
h5.d-flex
|
h5.d-flex
|
||||||
a.px-2(href="https://www.youtube.com/BlenderCloudOfficial",
|
a.px-2(href="https://twitter.com/Blender_Cloud",
|
||||||
title="Blender Cloud YouTube Channel")
|
title="Follow us on Twitter")
|
||||||
i.pi-social-youtube
|
i.pi-social-youtube
|
||||||
|
|
||||||
a.px-2(href="https://twitter.com/Blender_Cloud",
|
a.px-2(href="https://twitter.com/Blender_Cloud",
|
||||||
@@ -27,10 +27,9 @@
|
|||||||
|
|
||||||
.col-md-2.col-xs-6
|
.col-md-2.col-xs-6
|
||||||
h7.font-weight-bold
|
h7.font-weight-bold
|
||||||
a.d-block.pb-2(href="{{ url_for('cloud.learn') }}")
|
|
||||||
| TRAINING
|
| TRAINING
|
||||||
|
|
||||||
ul.list-unstyled.mb-3
|
ul.list-unstyled
|
||||||
li
|
li
|
||||||
a(href="{{ url_for('cloud.courses') }}")
|
a(href="{{ url_for('cloud.courses') }}")
|
||||||
| Courses
|
| Courses
|
||||||
@@ -38,16 +37,11 @@
|
|||||||
a(href="{{ url_for('cloud.workshops') }}")
|
a(href="{{ url_for('cloud.workshops') }}")
|
||||||
| Workshops
|
| Workshops
|
||||||
li
|
li
|
||||||
a(href="{{ url_for('cloud.production') }}")
|
a(href="{{ url_for('projects.view', project_url='gallery') }}")
|
||||||
span.new Production Lessons
|
| Art Gallery
|
||||||
|
|
||||||
h7.font-weight-bold
|
|
||||||
a.d-block.pb-2(href="{{ url_for('cloud.open_projects') }}")
|
|
||||||
| FILMS
|
|
||||||
|
|
||||||
.col-md-2.col-xs-6
|
.col-md-2.col-xs-6
|
||||||
h7.font-weight-bold
|
h7.font-weight-bold
|
||||||
a.d-block.pb-2(href="{{ url_for('cloud.libraries') }}")
|
|
||||||
| LIBRARIES
|
| LIBRARIES
|
||||||
|
|
||||||
ul.list-unstyled
|
ul.list-unstyled
|
||||||
@@ -63,13 +57,10 @@
|
|||||||
a(href="{{ url_for('projects.view', project_url='characters') }}",
|
a(href="{{ url_for('projects.view', project_url='characters') }}",
|
||||||
title="Characters")
|
title="Characters")
|
||||||
| Characters
|
| Characters
|
||||||
li
|
|
||||||
a(href="{{ url_for('projects.view', project_url='gallery') }}")
|
|
||||||
| Art Gallery
|
|
||||||
|
|
||||||
.col-md-2.col-xs-6
|
.col-md-2.col-xs-6
|
||||||
h7.font-weight-bold
|
h7.font-weight-bold
|
||||||
a.d-block.pb-2(href="{{ url_for('cloud.services') }}")
|
a(href="{{ url_for('cloud.services') }}")
|
||||||
| SERVICES
|
| SERVICES
|
||||||
|
|
||||||
ul.list-unstyled
|
ul.list-unstyled
|
||||||
@@ -96,9 +87,7 @@
|
|||||||
|
|
||||||
.col-md-2.col-xs-6
|
.col-md-2.col-xs-6
|
||||||
h7.font-weight-bold
|
h7.font-weight-bold
|
||||||
a.d-block.pb-2(href="{{ url_for('main.homepage') }}")
|
| RESOURCES
|
||||||
| CLOUD
|
|
||||||
|
|
||||||
ul.list-unstyled
|
ul.list-unstyled
|
||||||
li
|
li
|
||||||
a(href="{{ url_for('main.main_blog') }}",
|
a(href="{{ url_for('main.main_blog') }}",
|
||||||
@@ -111,8 +100,7 @@
|
|||||||
li
|
li
|
||||||
a(href="{{ url_for('cloud.privacy') }}",
|
a(href="{{ url_for('cloud.privacy') }}",
|
||||||
title="Privacy")
|
title="Privacy")
|
||||||
| Privacy Policy
|
| Privacy
|
||||||
li.dropdown-divider
|
|
||||||
li
|
li
|
||||||
a(href="https://www.blender.org",
|
a(href="https://www.blender.org",
|
||||||
title="Home of Blender, the Free and Open Source creative suite")
|
title="Home of Blender, the Free and Open Source creative suite")
|
||||||
|
@@ -1,174 +1,55 @@
|
|||||||
include ../../../../pillar/src/templates/mixins/components
|
include ../../../../pillar/src/templates/mixins/components
|
||||||
|
|
||||||
| {#
|
|
||||||
| Secondary Navigation Bars.
|
|
||||||
| #}
|
|
||||||
|
|
||||||
| {% macro navigation_homepage(title) %}
|
| {% macro navigation_homepage(title) %}
|
||||||
button.navbar-toggler(
|
+nav-secondary()
|
||||||
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(
|
||||||
href="{{ url_for('main.homepage') }}")
|
href="{{ url_for('cloud.open_projects') }}")
|
||||||
i.pi-blender-cloud-logo
|
|
||||||
|
|
||||||
+nav-secondary-link(
|
|
||||||
href="{{ url_for('cloud.open_projects') }}",
|
|
||||||
class="{% if title == 'films' %}active{% endif %}")
|
|
||||||
span Films
|
span Films
|
||||||
|
|
||||||
+nav-secondary-link(
|
+nav-secondary-link(
|
||||||
href="{{ url_for('cloud.learn') }}",
|
href="{{ url_for('cloud.courses') }}")
|
||||||
class="{% if title in ('learn', 'courses', 'workshops') %}active{% endif %}")
|
span Courses
|
||||||
span Training
|
|
||||||
|
|
||||||
+nav-secondary-link(
|
+nav-secondary-link(
|
||||||
href="{{ url_for('cloud.libraries') }}",
|
href="{{ url_for('cloud.workshops') }}")
|
||||||
class="{% if title == 'libraries' %}active{% endif %}")
|
span Workshops
|
||||||
span Libraries
|
|
||||||
|
|
||||||
+nav-secondary-link(
|
+nav-secondary-link(
|
||||||
href="{{ url_for('cloud.services') }}",
|
href="{{ url_for('projects.view', project_url='textures') }}")
|
||||||
class="{% if title == 'services' %}active{% endif %}")
|
span Textures
|
||||||
|
|
||||||
|
+nav-secondary-link(
|
||||||
|
href="{{ url_for('projects.view', project_url='hdri') }}")
|
||||||
|
span HDRI
|
||||||
|
|
||||||
|
+nav-secondary-link(
|
||||||
|
class="{% if title == 'services' %}active{% endif %}",
|
||||||
|
href="{{ url_for('cloud.services') }}")
|
||||||
span Services
|
span Services
|
||||||
| {% endmacro %}
|
| {% endmacro %}
|
||||||
|
|
||||||
|
| {% macro navigation_collection(title) %}
|
||||||
| {% macro navigation_home_project(title) %}
|
+nav-secondary
|
||||||
button.navbar-toggler(
|
| {% if title in ['courses', 'workshops', 'production'] %}
|
||||||
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(
|
||||||
href="{{ url_for('main.homepage') }}")
|
class="{% if title == 'courses' %}active{% endif %}",
|
||||||
i.pi-blender-cloud
|
href="{{ url_for('cloud.courses') }}")
|
||||||
|
span Courses
|
||||||
|
|
||||||
+nav-secondary-link(
|
+nav-secondary-link(
|
||||||
href="{{ url_for('projects.index') }}",
|
class="{% if title == 'workshops' %}active{% endif %}",
|
||||||
class="{% if title == 'dashboard' %}active{% endif %}")
|
href="{{ url_for('cloud.workshops') }}")
|
||||||
i.pi-star.pr-2
|
span Workshops
|
||||||
span My Projects
|
|
||||||
|
|
||||||
| {% if current_user.has_organizations() %}
|
|
||||||
+nav-secondary-link(
|
|
||||||
href="{{ url_for('pillar.web.organizations.index') }}",
|
|
||||||
class="{% if title == 'organizations' %}active{% endif %}")
|
|
||||||
i.pi-users.pr-2
|
|
||||||
span My Organizations
|
|
||||||
| {% endif %}
|
|
||||||
|
|
||||||
+nav-secondary-link(
|
+nav-secondary-link(
|
||||||
href="{{ url_for('projects.home_project_shared_images')}}",
|
class="{% if title == 'production' %}active{% endif %}",
|
||||||
class="{% if title == 'images' %}active{% endif %}")
|
href="{{ url_for('cloud.production') }}")
|
||||||
i.pi-picture.pr-2
|
span.new Production Lessons
|
||||||
span Image Sharing
|
|
||||||
|
|
||||||
|
| {% elif title in ['open-projects'] %}
|
||||||
+nav-secondary-link(
|
+nav-secondary-link(
|
||||||
href="{{ url_for('projects.home_project') }}",
|
class="{% if title == 'open-projects' %}active{% endif %}",
|
||||||
class="{% if title == 'blender-sync' %}active{% endif %}")
|
href="{{ url_for('projects.view', project_url='gallery') }}")
|
||||||
i.pi-blender.pr-2
|
span Open Projects
|
||||||
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 %}
|
||||||
|
@@ -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 %}
|
|
@@ -1,7 +0,0 @@
|
|||||||
| {% extends 'layout.html' %}
|
|
||||||
|
|
||||||
| {% block body %}
|
|
||||||
.row.py-2
|
|
||||||
.col.text-center
|
|
||||||
h1 Design System goes here
|
|
||||||
| {% endblock %}
|
|
@@ -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.
|
||||||
|
@@ -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 %}
|
|
@@ -25,54 +25,18 @@ meta(name="twitter:image", content="{% if main_project.picture_header %}{{ main_
|
|||||||
| {{ navigation_homepage(title) }}
|
| {{ navigation_homepage(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.col-xl-9
|
||||||
.d-xl-none
|
section.blog
|
||||||
+featured_projects()
|
| {% if latest_posts %}
|
||||||
|
| {% for node in latest_posts %}
|
||||||
+timeline()
|
| {{ render_blog_post(node) }}
|
||||||
|
| {% endfor %}
|
||||||
|
| {% else %}
|
||||||
|
| 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,17 +50,44 @@ 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.col-xl-3
|
||||||
.d-lg-none.d-xl-block
|
section.pt-3
|
||||||
+featured_projects()(class="card-deck-vertical border-bottom pb-3")
|
h6.title-underline
|
||||||
|
a.text-muted(href="{{ url_for('cloud.open_projects') }}")
|
||||||
|
| Films In Production
|
||||||
|
|
||||||
section.py-2.border-bottom.mb-3
|
a(href="/p/spring/")
|
||||||
|
img.rounded(
|
||||||
|
alt="Spring Open Movie Project",
|
||||||
|
src="{{ url_for('static', filename='assets/img/projects/spring_02_450x150.jpg')}}")
|
||||||
|
|
||||||
|
p.text-muted.pt-2.
|
||||||
|
A poetic short film about a mountain spirit and her wise little dog. #[a.text-muted(href="/p/spring/") Check it out].
|
||||||
|
|
||||||
|
section.py-3
|
||||||
|
h6.title-underline What's Going On
|
||||||
|
|
||||||
|
| {% if activity_stream %}
|
||||||
|
+card-deck()(class='card-deck-vertical pl-3')
|
||||||
|
| {% for child in activity_stream %}
|
||||||
|
| {% if child.node_type not in ['comment'] %}
|
||||||
|
| {{ asset_list_item(child, current_user) }}
|
||||||
|
| {% endif %}
|
||||||
|
| {% endfor %}
|
||||||
|
| {% else %}
|
||||||
|
.card
|
||||||
|
.card-body
|
||||||
|
h6.card-title
|
||||||
|
| No assets.
|
||||||
|
| {% endif %}
|
||||||
|
|
||||||
|
section.py-3.border-bottom.mb-3
|
||||||
h6.title-underline
|
h6.title-underline
|
||||||
a.text-muted(href="{{ url_for('main.nodes_search_index') }}")
|
a.text-muted(href="{{ url_for('main.nodes_search_index') }}")
|
||||||
| Random Awesome
|
| Random Awesome
|
||||||
|
|
||||||
| {% if random_featured %}
|
| {% if random_featured %}
|
||||||
+card-deck()(class='pl-3 random-featured')
|
+card-deck()(class='card-deck-vertical pl-3')
|
||||||
| {% for child in random_featured %}
|
| {% for child in random_featured %}
|
||||||
| {% if child.node_type not in ['comment'] %}
|
| {% if child.node_type not in ['comment'] %}
|
||||||
| {{ asset_list_item(child, current_user) }}
|
| {{ asset_list_item(child, current_user) }}
|
||||||
@@ -140,7 +131,6 @@ mixin featured_projects()
|
|||||||
|
|
||||||
| {% endif %}
|
| {% endif %}
|
||||||
|
|
||||||
|
|
||||||
| {% endblock %}
|
| {% endblock %}
|
||||||
|
|
||||||
|
|
||||||
|
@@ -32,9 +32,6 @@ html(lang="en")
|
|||||||
| {% 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/tutti.min.js') }}")
|
||||||
script.
|
|
||||||
pillar.utils.initCurrentUser({{ current_user | json | safe }});
|
|
||||||
script(src="{{ url_for('static_pillar', filename='assets/js/timeline.min.js') }}")
|
|
||||||
script(src="{{ url_for('static_pillar', filename='assets/js/vendor/jquery.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 %}
|
||||||
@@ -47,6 +44,7 @@ html(lang="en")
|
|||||||
| {% 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 +53,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,14 +67,29 @@ html(lang="en")
|
|||||||
| {% endwith %}
|
| {% endwith %}
|
||||||
|
|
||||||
nav.navbar.navbar-expand-md.fixed-top.bg-white
|
nav.navbar.navbar-expand-md.fixed-top.bg-white
|
||||||
|
+nav-secondary()
|
||||||
|
button.navbar-toggler(
|
||||||
|
data-target=".sarasa",
|
||||||
|
data-toggle="collapse",
|
||||||
|
type="button")
|
||||||
|
span.sr-only Toggle Navigation
|
||||||
|
span.navbar-toggler-icon.d-flex.align-items-center
|
||||||
|
i.pi-menu
|
||||||
|
|
||||||
|
li.nav-item.dropdown.large
|
||||||
|
a.nav-link.dropdown-toggle.px-2(
|
||||||
|
href="{{ url_for('main.homepage') }}"
|
||||||
|
data-toggle="dropdown")
|
||||||
|
i.pi-blender-cloud
|
||||||
|
i.pi-angle-down
|
||||||
|
|
||||||
|
| {% include 'menus/_dropdown_main.html' %}
|
||||||
|
|
||||||
| {% block navigation_tabs %}
|
| {% block navigation_tabs %}
|
||||||
+nav-secondary(class="collapse navbar-collapse")#navigationLinks
|
|
||||||
+nav-secondary-link(
|
|
||||||
href="{{ url_for('main.homepage') }}")
|
|
||||||
i.pi-blender-cloud-logo
|
|
||||||
| {% endblock navigation_tabs %}
|
| {% endblock navigation_tabs %}
|
||||||
+nav-secondary()(class="m-auto keep-when-overlay")
|
|
||||||
div.nav-item.quick-search.qs-input#qs-input
|
| {% block navigation_search %}
|
||||||
|
| {% endblock navigation_search %}
|
||||||
|
|
||||||
+nav-secondary()(class="ml-auto")
|
+nav-secondary()(class="ml-auto")
|
||||||
| {% if node and node.properties and node.properties.category %}
|
| {% if node and node.properties and node.properties.category %}
|
||||||
@@ -96,9 +97,17 @@ html(lang="en")
|
|||||||
| {% 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 %}
|
||||||
|
|
||||||
|
+nav-secondary-link(
|
||||||
|
href="{{ url_for('main.nodes_search_index') }}",
|
||||||
|
title="Search Blender Cloud",
|
||||||
|
data-toggle="tooltip",
|
||||||
|
data-placement="bottom",
|
||||||
|
class="py-2 px-2 text-muted")
|
||||||
|
i.pi-search
|
||||||
|
|
||||||
| {% endblock navigation_sections %}
|
| {% endblock navigation_sections %}
|
||||||
|
|
||||||
| {% block navigation_user %}
|
| {% block navigation_user %}
|
||||||
@@ -113,13 +122,10 @@ html(lang="en")
|
|||||||
title="Sign up") Sign up
|
title="Sign up") Sign up
|
||||||
| {% endif %}
|
| {% endif %}
|
||||||
|
|
||||||
.loading-bar
|
.loader-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 %}
|
||||||
@@ -139,6 +145,7 @@ html(lang="en")
|
|||||||
.nc-text
|
.nc-text
|
||||||
span.nc-date
|
span.nc-date
|
||||||
a(href="")
|
a(href="")
|
||||||
|
|
||||||
| {% 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.
|
||||||
@@ -168,36 +175,15 @@ html(lang="en")
|
|||||||
$('[data-toggle="tooltip"]').tooltip({'delay' : {'show': 0, 'hide': 0}});
|
$('[data-toggle="tooltip"]').tooltip({'delay' : {'show': 0, 'hide': 0}});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Enable Quick Search
|
// Main dropdown menu logic.
|
||||||
let searches = {
|
$('[data-toggle="dropdown-tab"]').hover(function(){
|
||||||
{% if project and not project.is_private %}
|
let tab = $(this).data('tab-target');
|
||||||
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({
|
$('[data-toggle="dropdown-tab"]').removeClass('active');
|
||||||
resultTarget: '#search-overlay',
|
$(this).addClass('active');
|
||||||
inputTarget: '#qs-input',
|
|
||||||
searches: searches,
|
$('[data-tab]').removeClass('show');
|
||||||
|
$('[data-tab="' + tab + '"]').addClass('show');
|
||||||
});
|
});
|
||||||
|
|
||||||
| {% block footer_scripts_pre %}{% endblock %}
|
| {% block footer_scripts_pre %}{% endblock %}
|
||||||
|
@@ -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 %}
|
|
@@ -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 %}
|
|
177
src/templates/menus/_dropdown_main.pug
Normal file
@@ -0,0 +1,177 @@
|
|||||||
|
include ../../../../pillar/src/templates/mixins/components
|
||||||
|
|
||||||
|
ul.dropdown-menu.nav-main
|
||||||
|
+nav-secondary()(
|
||||||
|
class="nav-secondary-vertical float-left bg-light border-left rounded-left")
|
||||||
|
+nav-secondary-link(
|
||||||
|
href="{{ url_for('main.homepage') }}",
|
||||||
|
data-toggle='dropdown-tab',
|
||||||
|
data-tab-target='home')
|
||||||
|
i.mr-2.pi-home
|
||||||
|
span Blender Cloud
|
||||||
|
|
||||||
|
+nav-secondary-link(
|
||||||
|
href="{{ url_for('cloud.open_projects') }}",
|
||||||
|
data-toggle='dropdown-tab',
|
||||||
|
data-tab-target='films')
|
||||||
|
i.mr-2.pi-film-thick
|
||||||
|
span Open Projects
|
||||||
|
|
||||||
|
li.nav-item
|
||||||
|
.nav-link(
|
||||||
|
data-toggle='dropdown-tab',
|
||||||
|
data-tab-target='training')
|
||||||
|
i.mr-2.pi-graduation-cap
|
||||||
|
span Learn
|
||||||
|
|
||||||
|
li.nav-item
|
||||||
|
.nav-link(
|
||||||
|
data-toggle='dropdown-tab',
|
||||||
|
data-tab-target='libraries')
|
||||||
|
i.mr-2.pi-file-archive
|
||||||
|
span Libraries
|
||||||
|
|
||||||
|
+nav-secondary-link(
|
||||||
|
href="{{ url_for('cloud.services') }}",
|
||||||
|
data-toggle='dropdown-tab',
|
||||||
|
data-tab-target='services',
|
||||||
|
class="{% if title == 'services' %}active{% endif %}")
|
||||||
|
i.mr-2.pi-whoosh
|
||||||
|
span Services
|
||||||
|
|
||||||
|
.dropdown-menu-tab(data-tab='home')
|
||||||
|
.dropdown-menu-column
|
||||||
|
+nav-secondary()(class="nav-secondary-vertical rounded-right border-left overflow-hidden")
|
||||||
|
+nav-secondary-link(
|
||||||
|
href="{{ url_for('main.main_blog') }}")
|
||||||
|
i.pi-newspaper
|
||||||
|
span Blog
|
||||||
|
|
||||||
|
+nav-secondary-link(
|
||||||
|
href="{{ url_for('projects.index') }}")
|
||||||
|
i.pi-star
|
||||||
|
span My Projects
|
||||||
|
|
||||||
|
| {% if current_user.has_organizations() %}
|
||||||
|
+nav-secondary-link(
|
||||||
|
href="{{ url_for('pillar.web.organizations.index') }}")
|
||||||
|
i.pi-users
|
||||||
|
span My Organizations
|
||||||
|
| {% endif %}
|
||||||
|
|
||||||
|
+nav-secondary-link(
|
||||||
|
href="{{ url_for('projects.home_project_shared_images')}}")
|
||||||
|
i.pi-picture
|
||||||
|
span Image Sharing
|
||||||
|
|
||||||
|
+nav-secondary-link(
|
||||||
|
href="{{ url_for('projects.home_project') }}")
|
||||||
|
i.pi-blender
|
||||||
|
span Blender Sync
|
||||||
|
|
||||||
|
.dropdown-menu-tab(data-tab='films')
|
||||||
|
.dropdown-menu-column
|
||||||
|
+nav-secondary()(class="nav-secondary-vertical rounded-right border-left overflow-hidden")
|
||||||
|
+nav-secondary-link(
|
||||||
|
href="{{ url_for('cloud.open_projects') }}",
|
||||||
|
class="nav-see-more border-bottom")
|
||||||
|
span.font-weight-bold
|
||||||
|
| All Open Projects
|
||||||
|
i.pi-angle-right.pl-2
|
||||||
|
|
||||||
|
+nav-secondary-link(
|
||||||
|
href="{{ url_for('projects.view', project_url='spring') }}")
|
||||||
|
span Spring
|
||||||
|
|
||||||
|
+nav-secondary-link(
|
||||||
|
href="{{ url_for('projects.view', project_url='hero') }}")
|
||||||
|
span Hero
|
||||||
|
|
||||||
|
+nav-secondary-link(
|
||||||
|
href="{{ url_for('projects.view', project_url='dailydweebs') }}")
|
||||||
|
span The Daily Dweebs
|
||||||
|
|
||||||
|
+nav-secondary-link(
|
||||||
|
href="{{ url_for('projects.view', project_url='agent-327') }}")
|
||||||
|
span Agent 327
|
||||||
|
|
||||||
|
.dropdown-menu-tab(data-tab='training')
|
||||||
|
.dropdown-menu-column
|
||||||
|
+nav-secondary()(class="nav-secondary-vertical rounded-right border-left overflow-hidden")
|
||||||
|
li.nav-item
|
||||||
|
.nav-link.border-bottom.pointer-events-none
|
||||||
|
span.font-weight-bold
|
||||||
|
| Learn
|
||||||
|
|
||||||
|
+nav-secondary-link(
|
||||||
|
href="{{ url_for('cloud.courses') }}")
|
||||||
|
i.pi-graduation-cap
|
||||||
|
span Courses
|
||||||
|
|
||||||
|
+nav-secondary-link(
|
||||||
|
href="{{ url_for('cloud.workshops') }}")
|
||||||
|
i.pi-lightbulb
|
||||||
|
span Workshops
|
||||||
|
|
||||||
|
+nav-secondary-link(
|
||||||
|
href="{{ url_for('cloud.production') }}")
|
||||||
|
i.pi-puzzle
|
||||||
|
span.new Production Lessons
|
||||||
|
|
||||||
|
.dropdown-menu-tab(data-tab='libraries')
|
||||||
|
.dropdown-menu-column
|
||||||
|
+nav-secondary()(class="nav-secondary-vertical rounded-right border-left overflow-hidden")
|
||||||
|
li.nav-item
|
||||||
|
.nav-link.border-bottom.pointer-events-none
|
||||||
|
span.font-weight-bold
|
||||||
|
| Libraries
|
||||||
|
|
||||||
|
+nav-secondary-link(
|
||||||
|
href="{{ url_for('projects.view', project_url='textures') }}")
|
||||||
|
i.pi-folder-texture
|
||||||
|
span Textures
|
||||||
|
|
||||||
|
+nav-secondary-link(
|
||||||
|
href="{{ url_for('projects.view', project_url='hdri') }}")
|
||||||
|
i.pi-globe
|
||||||
|
span HDRI
|
||||||
|
|
||||||
|
+nav-secondary-link(
|
||||||
|
href="{{ url_for('projects.view', project_url='characters') }}")
|
||||||
|
i.pi-character
|
||||||
|
span Characters
|
||||||
|
|
||||||
|
+nav-secondary-link(
|
||||||
|
href="{{ url_for('projects.view', project_url='gallery') }}")
|
||||||
|
i.pi-picture
|
||||||
|
span Art Gallery
|
||||||
|
|
||||||
|
.dropdown-menu-tab(data-tab='services')
|
||||||
|
.dropdown-menu-column
|
||||||
|
+nav-secondary()(class="nav-secondary-vertical rounded-right border-left overflow-hidden")
|
||||||
|
+nav-secondary-link(
|
||||||
|
href="{{ url_for('cloud.services') }}",
|
||||||
|
class="nav-see-more border-bottom")
|
||||||
|
span.font-weight-bold
|
||||||
|
| All Services
|
||||||
|
i.pi-angle-right.pl-2
|
||||||
|
|
||||||
|
+nav-secondary-link(
|
||||||
|
href="/attract")
|
||||||
|
i.pi-attract
|
||||||
|
span Attract
|
||||||
|
|
||||||
|
+nav-secondary-link(
|
||||||
|
href="/flamenco")
|
||||||
|
i.pi-attract
|
||||||
|
span Flamenco
|
||||||
|
|
||||||
|
+nav-secondary-link(
|
||||||
|
href="{{ url_for('cloud.services') }}#blender-cloud-add-on")
|
||||||
|
i.pi-blender
|
||||||
|
span Blender Cloud add-on
|
||||||
|
|
||||||
|
+nav-secondary-link(
|
||||||
|
href="{{ url_for('cloud.services') }}#texture-browser")
|
||||||
|
i.pi-texture
|
||||||
|
span Texture & HDRI Browser
|
@@ -10,7 +10,10 @@
|
|||||||
|
|
||||||
| {% 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="{{ url_for('settings.profile') }}", data-toggle="dropdown")
|
||||||
current-user-avatar
|
img.gravatar.rounded-circle(
|
||||||
|
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,8 +22,6 @@ 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 %}
|
||||||
|
|
||||||
|
|
||||||
|
@@ -1,33 +0,0 @@
|
|||||||
//- Category listing (Learn, Libraries, etc).
|
|
||||||
//- Header
|
|
||||||
mixin category_list_header(title, text)
|
|
||||||
.row.pt-2.pb-3.mb-4.border-bottom&attributes(attributes)
|
|
||||||
.col-md-9
|
|
||||||
h1.py-2.font-weight-bold
|
|
||||||
=title
|
|
||||||
|
|
||||||
.lead
|
|
||||||
=text
|
|
||||||
|
|
||||||
if block
|
|
||||||
block
|
|
||||||
|
|
||||||
//- List Item
|
|
||||||
mixin category_list_item(title, text, url, image, image_link_url)
|
|
||||||
.row.pb-2.my-2&attributes(attributes)
|
|
||||||
.col-md-8
|
|
||||||
a(href=url, title=title)
|
|
||||||
h3.font-weight-bold.text-muted
|
|
||||||
=title
|
|
||||||
.lead
|
|
||||||
=text
|
|
||||||
|
|
||||||
if block
|
|
||||||
block
|
|
||||||
|
|
||||||
.col-md-4
|
|
||||||
if image_link_url
|
|
||||||
- var url = image_link_url
|
|
||||||
|
|
||||||
a(href=url, title=title)
|
|
||||||
img.img-fluid.rounded(alt=title, src=image)
|
|
@@ -1,12 +1,13 @@
|
|||||||
include ../../../../../../pillar/src/templates/mixins/components
|
include ../../../../../../pillar/src/templates/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 }}{% if node.user.full_name %} · {{ node.user.full_name }}{% endif %}",
|
||||||
"{{ node.picture.thumbnail('h', api=api) }}",
|
"{{ node.picture.thumbnail('h', api=api) }}",
|
||||||
"{{ node.url }}")(
|
"{{ node.url }}")(
|
||||||
class="jumbotron-overlay")
|
class="jumbotron-overlay")
|
||||||
@@ -16,10 +17,6 @@ include ../../../../../../pillar/src/templates/mixins/components
|
|||||||
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 %}
|
||||||
@@ -41,21 +38,18 @@ include ../../../../../../pillar/src/templates/mixins/components
|
|||||||
|
|
||||||
hr.my-4
|
hr.my-4
|
||||||
|
|
||||||
comments-tree#comments-embed.justify-content-center.mx-auto(
|
#comments-embed.d-flex.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(
|
a.card.asset.card-image-fade.pr-0.mx-0.mb-4(
|
||||||
href="{{ node.url }}")
|
href="{{ node.url }}")
|
||||||
.card-thumbnail
|
.embed-responsive.embed-responsive-16by9
|
||||||
| {% if node.picture %}
|
| {% if node.picture %}
|
||||||
img.card-img-top(src="{{ node.picture.thumbnail('m', api=api) }}", alt="{{ node.name }}")
|
.card-img-top.embed-responsive-item(style="background-image: url({{ node.picture.thumbnail('m', api=api) }})")
|
||||||
| {% else %}
|
| {% else %}
|
||||||
.card-img-top
|
.card-img-top.card-icon.embed-responsive-item
|
||||||
i.pi-document-text
|
i.pi-document-text
|
||||||
| {% endif %}
|
| {% endif %}
|
||||||
|
|
||||||
@@ -76,14 +70,14 @@ a.card.asset.card-image-fade(
|
|||||||
//- ******************************************************* -//
|
//- ******************************************************* -//
|
||||||
| {% macro render_blog_index(current_post, project, posts, can_create_blog_posts, api, more_posts_available, posts_meta, pages=None) %}
|
| {% macro render_blog_index(current_post, project, posts, can_create_blog_posts, api, more_posts_available, posts_meta, pages=None) %}
|
||||||
|
|
||||||
| {% if can_create_blog_posts or (current_post and current_post.has_method('PUT')) %}
|
| {% if can_create_blog_posts or current_post.has_method('PUT') %}
|
||||||
+nav-secondary(class="bg-light border-bottom")
|
+nav-secondary
|
||||||
| {% if can_create_blog_posts %}
|
| {% if can_create_blog_posts %}
|
||||||
+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
|
i.pi-plus.pr-2
|
||||||
span Create New Blog Post
|
span Create New Blog Post
|
||||||
| {% endif %}
|
| {% endif %}
|
||||||
| {% if (current_post and current_post.has_method('PUT')) %}
|
| {% if current_post.has_method('PUT') %}
|
||||||
+nav-secondary-link(href="{{url_for('nodes.edit', node_id=current_post._id)}}")
|
+nav-secondary-link(href="{{url_for('nodes.edit', node_id=current_post._id)}}")
|
||||||
i.pi-edit.pr-2
|
i.pi-edit.pr-2
|
||||||
span Edit Post
|
span Edit Post
|
||||||
@@ -103,7 +97,7 @@ a.card.asset.card-image-fade(
|
|||||||
| More from {{ project.name }} blog
|
| More from {{ project.name }} blog
|
||||||
| {% endif %}
|
| {% endif %}
|
||||||
|
|
||||||
+card-deck(class="px-2")
|
+card-deck(class="px-2 justify-content-center")
|
||||||
| {% for node in posts %}
|
| {% for node in posts %}
|
||||||
| {# Skip listing the current post #}
|
| {# Skip listing the current post #}
|
||||||
| {% if node._id != current_post._id %}
|
| {% if node._id != current_post._id %}
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
| {% 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 ../../../../pillar/src/templates/mixins/components
|
||||||
|
|
||||||
| {% set title = 'organizations' %}
|
| {% set title = 'organizations' %}
|
||||||
@@ -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")
|
||||||
|
@@ -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%}
|
||||||
|
@@ -1,31 +1,20 @@
|
|||||||
| {% extends 'layout.html' %}
|
| {% extends 'layout.html' %}
|
||||||
| {% from '_macros/_navigation.html' import navigation_homepage %}
|
| {% from '_macros/_navigation.html' import navigation_collection %}
|
||||||
| {% from '_macros/_opengraph.html' import opengraph %}
|
|
||||||
include mixins/components
|
|
||||||
include ../../../pillar/src/templates/mixins/components
|
include ../../../pillar/src/templates/mixins/components
|
||||||
|
|
||||||
mixin group(title, tag)
|
mixin group(title, tag)
|
||||||
.row(id=tag)
|
.row
|
||||||
section.py-4.my-3.border-bottom.col-12
|
section.py-3.my-3.border-bottom.col-12
|
||||||
|
|
||||||
h4.title-underline.mt-2.mb-4
|
h4.title-underline.mb-4= title
|
||||||
a.text-muted(href="#" + tag)= title
|
+card-deck(data-asset-tag=tag, class="js-asset-list py-3")
|
||||||
+card-deck(data-asset-tag=tag, class="js-asset-list p-3")
|
|
||||||
|
|
||||||
| {% set title = 'learn' %}
|
| {% block page_title %}Production Lessons{% endblock %}
|
||||||
|
| {% set page_header_text = "Tips and tricks by the Open Movie crew." %}
|
||||||
| {% 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 %}
|
| {% block navigation_tabs %}
|
||||||
| {{ navigation_homepage(title) }}
|
| {{ navigation_collection(title) }}
|
||||||
| {% endblock navigation_tabs %}
|
| {% endblock navigation_tabs %}
|
||||||
|
|
||||||
| {% block head %}
|
| {% block head %}
|
||||||
@@ -33,16 +22,17 @@ 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 }};
|
$('.js-asset-list').loadTaggedAssets(5, 3);
|
||||||
$('.js-asset-list').loadTaggedAssets(8, 8, is_subscriber);
|
|
||||||
});
|
});
|
||||||
| {% endblock %}
|
| {% endblock %}
|
||||||
| {% block body %}
|
| {% block body %}
|
||||||
.container.py-4
|
+jumbotron(
|
||||||
+category_list_header('{{ page_title }}', '{{ page_description }}')
|
'{{ self.page_title() }}',
|
||||||
.row
|
'{{ page_header_text }}',
|
||||||
.col-12
|
"{{ url_for('static', filename='assets/img/backgrounds/background_agent327_04.jpg')}}")(
|
||||||
+group('Walk-through', 'walk-through')
|
class="jumbotron-overlay")
|
||||||
|
|
||||||
|
.container
|
||||||
+group('Modeling', 'modeling')
|
+group('Modeling', 'modeling')
|
||||||
+group('Sculpting', 'sculpting')
|
+group('Sculpting', 'sculpting')
|
||||||
+group('Animation', 'animation')
|
+group('Animation', 'animation')
|
||||||
@@ -53,8 +43,6 @@ script.
|
|||||||
+group('Lighting & Rendering', 'lighting')
|
+group('Lighting & Rendering', 'lighting')
|
||||||
+group('Simulation & Effects', 'effects')
|
+group('Simulation & Effects', 'effects')
|
||||||
+group('Video Editing', 'video-editing')
|
+group('Video Editing', 'video-editing')
|
||||||
+group('Digital Painting', 'digital-painting')
|
|
||||||
+group('Production Design', 'production-design')
|
|
||||||
|
|
||||||
a.d-block.py-5.text-center.text-muted(
|
a.d-block.py-5.text-center.text-muted(
|
||||||
href="{{ url_for('main.nodes_search_index') }}")
|
href="{{ url_for('main.nodes_search_index') }}")
|
||||||
|
@@ -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 %}
|
|
@@ -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 %}
|
|
@@ -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 %}
|
|
@@ -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 %}
|
|
@@ -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 %}
|
||||||
|
@@ -1,7 +1,9 @@
|
|||||||
| {% 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 ../../../../pillar/src/templates/mixins/components
|
||||||
|
|
||||||
|
| {% set title = 'home' %}
|
||||||
|
|
||||||
| {% block og %}
|
| {% block og %}
|
||||||
meta(property="og:type", content="website")
|
meta(property="og:type", content="website")
|
||||||
meta(property="og:url", content="https://cloud.blender.org{{ request.path }}")
|
meta(property="og:url", content="https://cloud.blender.org{{ request.path }}")
|
||||||
@@ -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')}}")
|
||||||
|
span Blender Sync
|
||||||
|
|
||||||
|
+nav-secondary-link(id="subtab-images", data-tab-url="{{ url_for('projects.home_project_shared_images')}}")
|
||||||
|
span Images
|
||||||
|
|
||||||
| {% block currenttab %}{% endblock %}
|
| {% block currenttab %}{% endblock %}
|
||||||
| {% endblock %}
|
| {% endblock %}
|
||||||
|
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
| {% 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 ../../../../pillar/src/templates/mixins/components
|
||||||
|
|
||||||
| {% set title = 'dashboard' %}
|
| {% set title = 'dashboard' %}
|
||||||
@@ -39,9 +39,10 @@ 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
|
||||||
|
@@ -1,25 +1,71 @@
|
|||||||
| {% 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
|
include ../../../../pillar/src/templates/mixins/components
|
||||||
|
|
||||||
| {% block bodyclasses %}{{ super() }} landing-home{% endblock %}
|
| {% import 'projects/_macros.html' as projectmacros %}
|
||||||
|
|
||||||
| {% from '_macros/_asset_list_item.html' import asset_list_item %}
|
| {% 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
|
||||||
@@ -28,155 +74,88 @@ include ../../../../pillar/src/templates/mixins/components
|
|||||||
| {% endblock %}
|
| {% endblock %}
|
||||||
|
|
||||||
| {% block css %}
|
| {% block css %}
|
||||||
|
link(href="{{ url_for('static_pillar', filename='assets/css/font-pillar.css') }}", rel="stylesheet")
|
||||||
link(href="{{ url_for('static_cloud', filename='assets/css/project-main.css') }}", rel="stylesheet")
|
link(href="{{ url_for('static_cloud', 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) }}
|
| {{ projectmacros.render_secondary_navigation(project, navigation_links, title) }}
|
||||||
| {% endblock navigation_tabs %}
|
| {% endblock navigation_tabs %}
|
||||||
|
|
||||||
| {% block body %}
|
| {% block body %}
|
||||||
|
+jumbotron(null, null, "{{ project.picture_header.thumbnail('h', api=api) }}")
|
||||||
| {% if project.has_method('PUT') %}
|
|
||||||
+nav-secondary
|
|
||||||
+nav-secondary-link(
|
|
||||||
href="{{ url_for('projects.edit', project_url=project.url) }}",
|
|
||||||
class="text-white")
|
|
||||||
i.pi-edit.pr-2
|
|
||||||
span Edit Project
|
|
||||||
| {% endif %}
|
|
||||||
|
|
||||||
| {% if project.picture_header %}
|
|
||||||
| {% set project_header = project.picture_header.thumbnail('h', api=api) %}
|
|
||||||
| {% endif %}
|
|
||||||
|
|
||||||
| {% 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
|
.container-fluid.landing
|
||||||
.row
|
.row
|
||||||
.col-md-8.mx-auto.mt-5
|
.col-md-8.mx-auto
|
||||||
.node-details-description
|
h2.pt-5 {{ project.name }}
|
||||||
|
|
||||||
| {% if project.description %}
|
| {% if project.description %}
|
||||||
|
.node-details-description
|
||||||
| {{ project | markdowned('description') }}
|
| {{ project | markdowned('description') }}
|
||||||
| {% endif %}
|
| {% endif %}
|
||||||
|
|
||||||
.row
|
.row
|
||||||
.col-md-10.mx-auto
|
.col-md-10.mx-auto
|
||||||
section.py-5
|
section.py-5
|
||||||
.gallery.mx-auto
|
h2.pb-3.text-center Gallery
|
||||||
|
|
||||||
|
.gallery
|
||||||
| {% 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.js-open-overlay(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 %}
|
||||||
|
| {% else %}
|
||||||
|
| {% set featured_node_id=project.nodes_featured[0] %}
|
||||||
|
| {% endif %}
|
||||||
|
.text-center.p-5
|
||||||
|
a.btn.btn-outline-secondary.px-5(
|
||||||
|
href="{{ url_for('projects.view_node', project_url=project.url, node_id=featured_node_id) }}")
|
||||||
| See More Artwork
|
| See More Artwork
|
||||||
|
| {% endif %}
|
||||||
|
|
||||||
|
|
||||||
.row.mt-5
|
.row
|
||||||
.col-md-10.mx-auto
|
.col-md-10.mx-auto
|
||||||
h2.pb-3 Project Timeline
|
|
||||||
.timeline-dark
|
h2.pb-3.text-center Latest Updates
|
||||||
+timeline("{{ project._id }}")
|
|
||||||
|
| {% if activity_stream %}
|
||||||
|
+card-deck(class="px-2")
|
||||||
|
| {% for n in activity_stream %}
|
||||||
|
| {% if n.node_type == 'post' %}
|
||||||
|
| {{ asset_list_item(n, current_user) }}
|
||||||
|
| {% endif %}
|
||||||
|
| {% endfor %}
|
||||||
|
| {% endif %}
|
||||||
|
|
||||||
|
.text-center.p-5
|
||||||
|
a.btn.btn-outline-secondary.px-5(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 +165,15 @@ script.
|
|||||||
hideOverlay();
|
hideOverlay();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$("a.js-open-overlay").on( "click", function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
|
||||||
|
$('#page-overlay').addClass('active');
|
||||||
|
var url = $(this).attr('href');
|
||||||
|
$('#page-overlay').html('<img src="' + url + '"/>')
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
| {% endblock %}
|
| {% endblock %}
|
||||||
|
@@ -1,14 +1,11 @@
|
|||||||
| {% 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 %}
|
| {% from 'projects/_macros.html' import render_secondary_navigation %}
|
||||||
|
|
||||||
include ../../../../pillar/src/templates/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")
|
||||||
@@ -85,41 +82,67 @@ script(src="{{ url_for('static_pillar', filename='assets/js/video_plugins.min.js
|
|||||||
| {% endblock %}
|
| {% endblock %}
|
||||||
|
|
||||||
| {% block css %}
|
| {% block css %}
|
||||||
|
link(href="{{ url_for('static_pillar', filename='assets/css/font-pillar.css') }}", rel="stylesheet")
|
||||||
link(href="{{ url_for('static_cloud', filename='assets/css/project-main.css') }}", rel="stylesheet")
|
link(href="{{ url_for('static_cloud', 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) }}
|
| {{ render_secondary_navigation(project, navigation_links, title) }}
|
||||||
| {% 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-browse.active(
|
||||||
|
title="Browse",
|
||||||
|
data-toggle="tooltip",
|
||||||
|
data-placement="right")
|
||||||
|
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(
|
|
||||||
type="button",
|
|
||||||
class="js-project-sidebar-toggle")
|
|
||||||
i.pi-angle-double-left
|
|
||||||
| {% block project_tree %}
|
| {% block project_tree %}
|
||||||
#project_tree.bg-light.p-1
|
#project_tree.bg-light.px-1.py-2.border-right
|
||||||
| {% endblock project_tree %}
|
| {% endblock project_tree %}
|
||||||
| {% endif %}
|
| {% endif %}
|
||||||
|
|
||||||
|
|
||||||
#project_context-container
|
#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'});
|
|
||||||
|
|
||||||
| {% if project.has_method('PUT') %}
|
| {% if project.has_method('PUT') %}
|
||||||
#project_context-header.position-absolute
|
#project_context-header.position-fixed
|
||||||
ul.project-edit-tools.disabled.d-flex.list-unstyled.py-2.mb-0
|
ul.project-edit-tools.disabled.d-flex.list-unstyled.py-2.mb-0
|
||||||
li.dropdown(
|
li.dropdown(
|
||||||
title="Create...",
|
title="Create...",
|
||||||
@@ -240,8 +263,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 +292,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 +349,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();
|
$('.loader-bar').removeClass('active');
|
||||||
}
|
}
|
||||||
| {% endblock %}
|
| {% endblock %}
|
||||||
|
|
||||||
@@ -362,36 +377,31 @@ script.
|
|||||||
|
|
||||||
|
|
||||||
function loadNodeContent(url, nodeId) {
|
function loadNodeContent(url, nodeId) {
|
||||||
|
$('.loader-bar').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')
|
$('.loader-bar').removeClass('active');
|
||||||
$('.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')
|
$('.loader-bar').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 +409,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')
|
$('.loader-bar').removeClass('active');
|
||||||
$('.button-edit-icon').addClass('pi-edit').removeClass('pi-spin spin');
|
$('.button-edit-icon').addClass('pi-edit').removeClass('pi-spin spin');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -497,11 +507,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);
|
||||||
}
|
}
|
||||||
|
@@ -1,23 +1,40 @@
|
|||||||
| {% extends 'layout.html' %}
|
| {% extends 'layout.html' %}
|
||||||
| {% from '_macros/_navigation.html' import navigation_homepage %}
|
| {% from '_macros/_navigation.html' import navigation_collection %}
|
||||||
| {% from '_macros/_opengraph.html' import opengraph %}
|
|
||||||
|
|
||||||
include ../../../pillar/src/templates/mixins/components
|
include ../../../pillar/src/templates/mixins/components
|
||||||
include mixins/components
|
|
||||||
|
|
||||||
| {# Default collection is 'Courses' #}
|
| {# Default case is Open Projects #}
|
||||||
|
| {% set page_title = 'Open Projects' %}
|
||||||
|
| {% 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_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 == 'courses' %}
|
||||||
| {% set page_title = 'Courses' %}
|
| {% set page_title = 'Courses' %}
|
||||||
| {% set page_description = 'In-depth training on character modeling, 3D printing, rigging, VFX and more.' %}
|
| {% 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 = 'Character modeling, 3D printing, VFX, rigging and more.' %}
|
||||||
|
|
||||||
| {% if title == 'workshops' %}
|
| {% 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 +42,53 @@ include mixins/components
|
|||||||
| {% endblock %}
|
| {% endblock %}
|
||||||
|
|
||||||
| {% block navigation_tabs %}
|
| {% block navigation_tabs %}
|
||||||
| {{ navigation_homepage(title) }}
|
| {{ navigation_collection(title) }}
|
||||||
| {% endblock navigation_tabs %}
|
| {% endblock navigation_tabs %}
|
||||||
|
|
||||||
| {% block body %}
|
| {% block body %}
|
||||||
.container.py-4
|
|
||||||
+category_list_header('{{ page_title }}', '{{ page_description }}', '{{ request.url }}')
|
|
||||||
|
|
||||||
+card-deck()
|
| {# Specify the URL of projects in production. These are hidden from the listing below. #}
|
||||||
|
| {% set projects_in_production = ['spring'] %}
|
||||||
|
- var jumbotron_title = 'SPRING';
|
||||||
|
- var jumbotron_lead = 'A poetic short film about a mountain spirit and her wise little dog. Check it out.';
|
||||||
|
+jumbotron(
|
||||||
|
jumbotron_title,
|
||||||
|
jumbotron_lead,
|
||||||
|
"{{ url_for('static', filename='assets/img/backgrounds/background_spring_01.jpg')}}")(
|
||||||
|
class="jumbotron-overlay")
|
||||||
|
|
||||||
|
a.btn.btn-primary.mt-4.px-4(
|
||||||
|
href="{{ url_for('projects.view', project_url='spring') }}") Browse the Project
|
||||||
|
a.btn.btn-link-light.mt-4(
|
||||||
|
style="color: white",
|
||||||
|
href="{{ url_for('main.project_blog', project_url='spring') }}")
|
||||||
|
| Read the Blog
|
||||||
|
i.pi-angle-right.px-1
|
||||||
|
|
||||||
|
.container.pb-5
|
||||||
|
.row
|
||||||
|
.col-12
|
||||||
|
.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.url not in projects_in_production %}
|
||||||
| {% 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(
|
||||||
class='js-project-go card-fade cursor-pointer mb-4',
|
class='js-project-go card-fade cursor-pointer mb-4 mx-0',
|
||||||
style="min-width: 30%",
|
|
||||||
data-url="{{ url_for('projects.view', project_url=project.url) }}",
|
data-url="{{ url_for('projects.view', project_url=project.url) }}",
|
||||||
tabindex='{{ loop.index }}')
|
tabindex='{{ loop.index }}')
|
||||||
|
| {% if project.picture_header %}
|
||||||
| {% if project.picture_16_9 %}
|
a(href="{{ url_for('projects.view', project_url=project.url) }}")
|
||||||
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
|
||||||
@@ -61,6 +103,7 @@ include mixins/components
|
|||||||
| {{project.summary|safe}}
|
| {{project.summary|safe}}
|
||||||
| {% endif %}
|
| {% endif %}
|
||||||
| {% endif %}
|
| {% endif %}
|
||||||
|
| {% endif %}
|
||||||
| {% endfor %}
|
| {% endfor %}
|
||||||
|
|
||||||
| {% endblock %}
|
| {% endblock %}
|
||||||
|
@@ -1,19 +1,20 @@
|
|||||||
| {% extends 'layout.html' %}
|
| {% extends 'layout.html' %}
|
||||||
| {% from '_macros/_navigation.html' import navigation_homepage %}
|
| {% from '_macros/_navigation.html' import navigation_homepage %}
|
||||||
| {% from '_macros/_opengraph.html' import opengraph %}
|
|
||||||
include ../../../pillar/src/templates/mixins/components
|
|
||||||
include mixins/components
|
|
||||||
|
|
||||||
|
| {% block page_title %}Services{% endblock %}
|
||||||
| {% set title = 'services' %}
|
| {% set title = 'services' %}
|
||||||
|
include ../../../pillar/src/templates/mixins/components
|
||||||
| {% 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")
|
||||||
|
meta(property="og:url", content="{{ request.url }}")
|
||||||
|
|
||||||
|
meta(property="og:title", content="Services - Blender Cloud")
|
||||||
|
meta(name="twitter:title", content="Services - Blender Cloud")
|
||||||
|
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 %}
|
| {% endblock %}
|
||||||
|
|
||||||
| {% block navigation_tabs %}
|
| {% block navigation_tabs %}
|
||||||
@@ -26,8 +27,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')}}")(class="jumbotron-overlay")
|
||||||
|
|
||||||
- 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 +46,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 <small>v</small> {{ config.BLENDER_CLOUD_ADDON_VERSION }}
|
| Download add-on <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 +68,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 <small>v</small> {{ config.BLENDER_CLOUD_ADDON_VERSION }}
|
| Download add-on <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 +95,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 +115,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 +137,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 +165,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 +189,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')}}")
|
||||||
|
|
||||||
|
@@ -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.
|
||||||
|
@@ -29,115 +29,52 @@ meta(property="og:image", content="{{ url_for('static', filename='assets/img/bac
|
|||||||
li.pr-1
|
li.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-sm.btn-outline-primary.px-3(href="{{ url_for('users.login', next='/') }}")
|
||||||
| Log in & Browse
|
| Log in & Explore
|
||||||
| {% else %}
|
| {% else %}
|
||||||
a.btn.btn-sm.btn-outline-primary.px-3(href="{{ url_for('main.homepage') }}")
|
a.btn.btn-sm.btn-outline-primary.px-3(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 +111,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')}})")
|
||||||
|
Before Width: | Height: | Size: 116 KiB |
Before Width: | Height: | Size: 138 KiB |
Before Width: | Height: | Size: 48 KiB |
Before Width: | Height: | Size: 37 KiB |
Before Width: | Height: | Size: 71 KiB |
Before Width: | Height: | Size: 90 KiB |
Before Width: | Height: | Size: 52 KiB |
Before Width: | Height: | Size: 75 KiB |
Before Width: | Height: | Size: 75 KiB |
Before Width: | Height: | Size: 96 KiB |
Before Width: | Height: | Size: 47 KiB |
Before Width: | Height: | Size: 24 KiB |
Before Width: | Height: | Size: 57 KiB |
Before Width: | Height: | Size: 139 KiB |
Before Width: | Height: | Size: 58 KiB |
Before Width: | Height: | Size: 39 KiB |
Before Width: | Height: | Size: 129 KiB |
Before Width: | Height: | Size: 55 KiB |
Before Width: | Height: | Size: 27 KiB |
Before Width: | Height: | Size: 38 KiB |
Before Width: | Height: | Size: 43 KiB |