Compare commits
237 Commits
flamencoRe
...
production
Author | SHA1 | Date | |
---|---|---|---|
![]() |
e7886261bd | ||
81c5687f02 | |||
![]() |
6b835e74f1 | ||
b6127c736d | |||
7280f1dbc1 | |||
28ee78ec02 | |||
96a54695af | |||
cbdcd04423 | |||
c618e6cf17 | |||
b8defe329e | |||
cf887d8f5f | |||
11effcb580 | |||
35ba45445f | |||
6c720b7b08 | |||
344a66f0eb | |||
941ed4a0e0 | |||
acfffce48d | |||
b6b097483a | |||
b76be2a7ba | |||
c168c09293 | |||
db74c89e6f | |||
23d7e50df2 | |||
a8afccba00 | |||
24118b6777 | |||
76825fda39 | |||
d49f69ecbd | |||
c91a52046d | |||
577bf8b964 | |||
049d71dc77 | |||
2075c8a790 | |||
2d6b5d4b67 | |||
5f497fc645 | |||
a321d3501a | |||
d2d4a52846 | |||
ae176cbbdf | |||
d5f5b63b3f | |||
274766d6f4 | |||
e74d573063 | |||
f514cc4176 | |||
3d567ff6f8 | |||
ba69dd46a0 | |||
cfbb3d7e5a | |||
c9bbf26a71 | |||
35675866ee | |||
d813935f43 | |||
947dab3185 | |||
c0cb80ceec | |||
48c8f79371 | |||
53b22641f2 | |||
4f9699c7ae | |||
a04e62e3e9 | |||
5df18b670a | |||
a5cd12ad87 | |||
2c51407196 | |||
0e0716449a | |||
f0c4f1576c | |||
42edd9486b | |||
8534cdbaeb | |||
7209a3c525 | |||
81a564a9d9 | |||
1c47197fd2 | |||
ea95e7b2b2 | |||
be28b2d13d | |||
ea9da2acdb | |||
c66592a5ae | |||
1e3041d997 | |||
f5db3c8da2 | |||
224e4dc1e0 | |||
c1e52ae320 | |||
b8be19c729 | |||
51d081971c | |||
b438c319b0 | |||
87bc2b5378 | |||
c3261ed83a | |||
468fc85751 | |||
049fdf3b63 | |||
ad31b6338f | |||
61b1ab0c20 | |||
08de073464 | |||
a756fb5f6e | |||
2050f4b7d8 | |||
51e22eb414 | |||
cabfce12c0 | |||
b6d9039e82 | |||
4a6e971e71 | |||
eeab0a407e | |||
eafc1e981f | |||
dfbd03e448 | |||
12c64f13a2 | |||
b62f500b2e | |||
6d47946b1b | |||
b4ecf93485 | |||
3d6b2452d6 | |||
4afe23e284 | |||
479153af9b | |||
aee369cc5a | |||
8f0670d017 | |||
858bed66f4 | |||
7a02f86a5b | |||
37667424ab | |||
b4739521ed | |||
d125a6ac55 | |||
4473d56379 | |||
c971f799ef | |||
36f31caf04 | |||
d54d5ec157 | |||
4a180e3784 | |||
6ac75c6a14 | |||
4b7cc3f58e | |||
cbbaf90002 | |||
c5154240ca | |||
1e62aff62c | |||
b015cc8fa4 | |||
3be54da5c3 | |||
5c1b94544a | |||
1ed2a3937e | |||
80b69438ed | |||
3f3112f272 | |||
1d7cbd8ba5 | |||
a53dc51680 | |||
29ff9609e7 | |||
bd07a63acd | |||
9bf6c0fcb3 | |||
142dd36e3c | |||
e03cf2f5da | |||
79b409c83a | |||
a67af63459 | |||
362694f4b6 | |||
364fa76956 | |||
144dcf7a76 | |||
f8995fb657 | |||
95762acf14 | |||
35a9986290 | |||
e8c878d0f3 | |||
fdf05d16de | |||
49bffd108d | |||
87c1eae1a6 | |||
163aee6bbc | |||
5f01b0d6ba | |||
da955ce4af | |||
ad1a55c5d5 | |||
db976a4d50 | |||
42215d2b02 | |||
9cf3a7147e | |||
fb016c3e3b | |||
039ab1ce70 | |||
1bd1c6e5fd | |||
353660c7ba | |||
b77f3aaa49 | |||
50c0842e72 | |||
5d2ba7fc96 | |||
6972e1662d | |||
af6022c997 | |||
2fc1738e99 | |||
149013d64c | |||
e13a1bff1b | |||
9ac60fe922 | |||
2f78f36f61 | |||
5edcf931e9 | |||
b417f25811 | |||
691c1411bc | |||
9f3946ba9e | |||
269daa0d43 | |||
39516e63cc | |||
84881743ef | |||
e975492869 | |||
78e1c728fa | |||
be18dfb985 | |||
8797b18754 | |||
727ba3fd58 | |||
a369c04b38 | |||
fad5f803e8 | |||
143cd27c55 | |||
2f854ebeee | |||
6fa3af50cf | |||
1fd2443bd7 | |||
c5f8add5f5 | |||
a8e5d593ac | |||
fc986b0ab6 | |||
b7b6543043 | |||
d32c44e50c | |||
369161e29f | |||
3cd55e2a83 | |||
1071915f27 | |||
5f9406edd2 | |||
5bf1693d5b | |||
27caff7e6e | |||
63d25d1dca | |||
2950a4347a | |||
76a707e5bf | |||
5218bd17e3 | |||
37fe235d47 | |||
1a49b24f8e | |||
0b2a3c99ce | |||
a674de4db5 | |||
d2815acd80 | |||
670c600382 | |||
f2207bc4d4 | |||
a9848c3fad | |||
c81711de53 | |||
2a8a109d83 | |||
35b1106ccc | |||
1f326b2728 | |||
ccb3187c17 | |||
734a8db145 | |||
82c6c30a0a | |||
594af19b2b | |||
3de73ac35e | |||
97c549de08 | |||
b5ff89f4ca | |||
ef8bd8d22b | |||
4ff52a8af0 | |||
218ba3831c | |||
a753f29ccc | |||
1a7be4b565 | |||
765f36261a | |||
bce054d47d | |||
89ea34724b | |||
7983a7b038 | |||
7ba8ff7580 | |||
fd1db5d2e0 | |||
205e34289f | |||
e7190f09dc | |||
7e61d218b9 | |||
12936c80ea | |||
dcac1317bf | |||
afa0c96156 | |||
012eaaef11 | |||
54e4e76945 | |||
6cbd5ca369 | |||
690b35bab1 | |||
356e4705b3 | |||
748190d15b | |||
05ff27a12d | |||
74e18bb500 | |||
c460359b31 | |||
14daead15d |
1
.gitignore
vendored
@@ -21,6 +21,7 @@ node_modules/
|
||||
/docker/2_buildpy/python/
|
||||
/docker/4_run/wheelhouse/
|
||||
/docker/4_run/deploy/
|
||||
/docker/4_run/staging/
|
||||
/celerybeat-schedule.bak
|
||||
/celerybeat-schedule.dat
|
||||
/celerybeat-schedule.dir
|
||||
|
30
README.md
@@ -27,14 +27,19 @@ git clone git://git.blender.org/blender-cloud.git
|
||||
|
||||
### Initial setup and configuration
|
||||
|
||||
Create a virtualenv for the project and install the requirements:
|
||||
Create a virtualenv for the project and install the requirements. Dependencies are managed via
|
||||
[Poetry](https://poetry.eustace.io/). Install it using `pip install -U --user poetry`.
|
||||
|
||||
```
|
||||
cd blender-cloud
|
||||
mkvirtualenv blender-cloud -p python3.6
|
||||
pip install -r requirements-dev.txt
|
||||
pip install --user -U poetry
|
||||
poetry install
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
```
|
||||
@@ -51,7 +56,7 @@ cp config_local.example.py config_local.py
|
||||
Setup the database with the initial collections and the admin user.
|
||||
|
||||
```
|
||||
./manage.py setup setup_db your_email
|
||||
poetry run ./manage.py setup setup_db your_email
|
||||
```
|
||||
|
||||
The command will return the following message:
|
||||
@@ -65,7 +70,7 @@ Copy the value of `<project_id>` and assign it as value for `MAIN_PROJECT_ID`.
|
||||
Run the application:
|
||||
|
||||
```
|
||||
./manage.py runserver
|
||||
poetry run ./manage.py runserver
|
||||
```
|
||||
|
||||
|
||||
@@ -102,7 +107,7 @@ git stash # if you still have local stuff.
|
||||
|
||||
# pull from master, run unittests, push your changes to master.
|
||||
git pull
|
||||
py.test
|
||||
poetry run py.test
|
||||
git push
|
||||
|
||||
# Switch to production branch, and investigate the situation.
|
||||
@@ -112,7 +117,7 @@ git prod
|
||||
git ff master
|
||||
|
||||
# Run tests again
|
||||
py.test
|
||||
poetry run py.test
|
||||
|
||||
# Push the production branch.
|
||||
git push
|
||||
@@ -120,13 +125,4 @@ git push
|
||||
|
||||
## Deploying to production server
|
||||
|
||||
```
|
||||
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.
|
||||
See [deploy/README.md](deploy/README.md).
|
||||
|
@@ -3,6 +3,8 @@ import logging
|
||||
import flask
|
||||
from werkzeug.local import LocalProxy
|
||||
|
||||
import pillarsdk
|
||||
import pillar.auth
|
||||
from pillar.api.utils import authorization
|
||||
from pillar.extension import PillarExtension
|
||||
|
||||
@@ -42,7 +44,8 @@ class CloudExtension(PillarExtension):
|
||||
'EXTERNAL_SUBSCRIPTIONS_TIMEOUT_SECS': 10,
|
||||
'BLENDER_ID_WEBHOOK_USER_CHANGED_SECRET': 'oos9wah1Zoa0Yau6ahThohleiChephoi',
|
||||
'NODE_TAGS': ['animation', 'modeling', 'rigging', 'sculpting', 'shading', 'texturing', 'lighting',
|
||||
'character-pipeline', 'effects', 'video-editing'],
|
||||
'character-pipeline', 'effects', 'video-editing', 'digital-painting', 'production-design',
|
||||
'walk-through'],
|
||||
}
|
||||
|
||||
def eve_settings(self):
|
||||
@@ -86,6 +89,54 @@ class CloudExtension(PillarExtension):
|
||||
'current_user_is_subscriber': authorization.user_has_cap('subscriber')
|
||||
}
|
||||
|
||||
def is_cloud_project(self, project):
|
||||
"""Returns whether the project is set up for Blender Cloud.
|
||||
|
||||
Requires the presence of the 'cloud' key in extension_props
|
||||
"""
|
||||
|
||||
if project.extension_props is None:
|
||||
# There are no extension_props on this project
|
||||
return False
|
||||
|
||||
try:
|
||||
pprops = project.extension_props[EXTENSION_NAME]
|
||||
except AttributeError:
|
||||
self._log.warning("is_cloud_project: Project url=%r doesn't have any "
|
||||
"extension properties.", project['url'])
|
||||
if self._log.isEnabledFor(logging.DEBUG):
|
||||
import pprint
|
||||
self._log.debug('Project: %s', pprint.pformat(project.to_dict()))
|
||||
return False
|
||||
except KeyError:
|
||||
# Not set up for Blender Cloud
|
||||
return False
|
||||
|
||||
if pprops is None:
|
||||
self._log.debug("is_cloud_project: Project url=%r doesn't have Blender Cloud"
|
||||
" extension properties.", project['url'])
|
||||
return False
|
||||
return True
|
||||
|
||||
@property
|
||||
def has_project_settings(self) -> bool:
|
||||
# Only available for admins
|
||||
return pillar.auth.current_user.has_cap('admin')
|
||||
|
||||
def project_settings(self, project: pillarsdk.Project, **template_args: dict) -> flask.Response:
|
||||
"""Renders the project settings page for this extension.
|
||||
|
||||
Set YourExtension.has_project_settings = True and Pillar will call this function.
|
||||
|
||||
:param project: the project for which to render the settings.
|
||||
:param template_args: additional template arguments.
|
||||
:returns: a Flask HTTP response
|
||||
"""
|
||||
|
||||
from cloud.routes import project_settings
|
||||
|
||||
return project_settings(project, **template_args)
|
||||
|
||||
def setup_app(self, app):
|
||||
from . import routes, webhooks, eve_hooks, email
|
||||
|
||||
|
10
cloud/cli.py
@@ -9,6 +9,8 @@ import requests
|
||||
|
||||
from pillar.cli import manager
|
||||
from pillar.api import service
|
||||
from pillar.api.utils import authentication
|
||||
import cloud.setup
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
@@ -126,4 +128,12 @@ def reconcile_subscribers():
|
||||
log.info(' skipped : %d', count_skipped)
|
||||
|
||||
|
||||
@manager_cloud.command
|
||||
def setup_for_film(project_url):
|
||||
"""Adds Blender Cloud film custom properties to a project."""
|
||||
|
||||
authentication.force_cli_user()
|
||||
cloud.setup.setup_for_film(project_url)
|
||||
|
||||
|
||||
manager.add_command("cloud", manager_cloud)
|
||||
|
15
cloud/forms.py
Normal file
@@ -0,0 +1,15 @@
|
||||
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')
|
339
cloud/routes.py
@@ -3,23 +3,32 @@ import json
|
||||
import logging
|
||||
import typing
|
||||
|
||||
from flask_login import current_user, login_required
|
||||
import bson
|
||||
from flask_login import login_required
|
||||
import flask
|
||||
from flask import Blueprint, render_template, redirect, session, url_for, abort, flash
|
||||
import werkzeug.exceptions as wz_exceptions
|
||||
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.exceptions import ResourceNotFound
|
||||
|
||||
import pillar
|
||||
import pillarsdk
|
||||
from pillar import current_app
|
||||
import pillar.api
|
||||
from pillar.api.utils import authorization
|
||||
from pillar.auth import current_user
|
||||
from pillar.web.users import forms
|
||||
from pillar.web.utils import system_util, get_file, current_user_is_authenticated
|
||||
from pillar.web.utils import attach_project_pictures
|
||||
from pillar.web.settings import blueprint as blueprint_settings
|
||||
from pillar.web.nodes.routes import url_for_node
|
||||
from pillar.web.nodes.custom.comments import render_comments_for_node
|
||||
from pillar.web.projects.routes import render_project
|
||||
from pillar.web.projects.routes import find_project_or_404
|
||||
from pillar.web.projects.routes import project_view
|
||||
from pillar.web.projects.routes import project_navigation_links
|
||||
|
||||
from cloud import current_cloud
|
||||
from cloud.forms import FilmProjectForm
|
||||
from . import EXTENSION_NAME
|
||||
|
||||
blueprint = Blueprint('cloud', __name__)
|
||||
log = logging.getLogger(__name__)
|
||||
@@ -42,35 +51,6 @@ def _homepage_context() -> dict:
|
||||
|
||||
# Get latest blog posts
|
||||
api = system_util.pillar_api()
|
||||
latest_posts = Node.all({
|
||||
'projection': {
|
||||
'name': 1,
|
||||
'project': 1,
|
||||
'node_type': 1,
|
||||
'picture': 1,
|
||||
'properties.url': 1,
|
||||
'properties.content': 1,
|
||||
'properties.attachments': 1
|
||||
},
|
||||
|
||||
'where': {'node_type': 'post', 'properties.status': 'published'},
|
||||
'embedded': {'project': 1},
|
||||
'sort': '-_created',
|
||||
'max_results': '3'
|
||||
}, api=api)
|
||||
|
||||
# Append picture Files to last_posts
|
||||
for post in latest_posts._items:
|
||||
post.picture = get_file(post.picture, api=api)
|
||||
post.url = url_for_node(node=post)
|
||||
|
||||
# Get latest assets added to any project
|
||||
latest_assets = Node.latest('assets', api=api)
|
||||
|
||||
# Append picture Files to latest_assets
|
||||
for asset in latest_assets._items:
|
||||
asset.picture = get_file(asset.picture, api=api)
|
||||
asset.url = url_for_node(node=asset)
|
||||
|
||||
# Get latest comments to any node
|
||||
latest_comments = Node.latest('comments', api=api)
|
||||
@@ -89,6 +69,7 @@ def _homepage_context() -> dict:
|
||||
'name': 1,
|
||||
'node_type': 1,
|
||||
'project': 1,
|
||||
'parent': 1,
|
||||
'properties.url': 1,
|
||||
}},
|
||||
api=api)
|
||||
@@ -115,23 +96,24 @@ def _homepage_context() -> dict:
|
||||
main_project = Project.find(current_app.config['MAIN_PROJECT_ID'], api=api)
|
||||
main_project.picture_header = get_file(main_project.picture_header, api=api)
|
||||
|
||||
# Merge latest assets and comments into one activity stream.
|
||||
def sort_key(item):
|
||||
return item._created
|
||||
|
||||
activity_stream = sorted(latest_assets._items, key=sort_key, reverse=True)
|
||||
|
||||
for node in activity_stream:
|
||||
node.url = url_for_node(node=node)
|
||||
|
||||
return dict(
|
||||
main_project=main_project,
|
||||
latest_posts=latest_posts._items,
|
||||
latest_comments=latest_comments._items,
|
||||
activity_stream=activity_stream,
|
||||
random_featured=random_featured)
|
||||
|
||||
|
||||
@blueprint.route('/design-system')
|
||||
def design_system():
|
||||
"""Display the design system page.
|
||||
|
||||
This endpoing is intended for development only, and returns a
|
||||
rendered template only if the app is running in debug mode.
|
||||
"""
|
||||
if not current_app.config['DEBUG']:
|
||||
abort(404)
|
||||
return render_template('design_system.html')
|
||||
|
||||
|
||||
@blueprint.route('/login')
|
||||
def login():
|
||||
from flask import request
|
||||
@@ -169,6 +151,16 @@ def services():
|
||||
return render_template('services.html')
|
||||
|
||||
|
||||
@blueprint.route('/learn')
|
||||
def learn():
|
||||
return render_template('learn.html')
|
||||
|
||||
|
||||
@blueprint.route('/libraries')
|
||||
def libraries():
|
||||
return render_template('libraries.html')
|
||||
|
||||
|
||||
@blueprint.route('/stats')
|
||||
def stats():
|
||||
return render_template('stats.html')
|
||||
@@ -219,10 +211,35 @@ def courses():
|
||||
def open_projects():
|
||||
@current_app.cache.cached(timeout=3600, unless=current_user_is_authenticated)
|
||||
def render_page():
|
||||
projects = get_projects('film')
|
||||
api = system_util.pillar_api()
|
||||
projects = Project.all({
|
||||
'where': {
|
||||
'category': 'film',
|
||||
'is_private': False
|
||||
},
|
||||
'sort': '-_created',
|
||||
}, api=api)
|
||||
for project in projects._items:
|
||||
# Attach poster file (ensure the extension_props.cloud.poster
|
||||
# attributes exists)
|
||||
try:
|
||||
# If the attribute exists, but is None, continue
|
||||
if not project['extension_props'][EXTENSION_NAME]['poster']:
|
||||
continue
|
||||
# Fetch the file and embed it in the document
|
||||
project.extension_props.cloud.poster = get_file(
|
||||
project.extension_props.cloud.poster, api=api)
|
||||
# Add convenience attribute that specifies the presence of the
|
||||
# poster file
|
||||
project.has_poster = True
|
||||
# If there was a key error because one of the nested attributes is
|
||||
# missing,
|
||||
except KeyError:
|
||||
continue
|
||||
|
||||
return render_template(
|
||||
'projects_index_collection.html',
|
||||
title='open-projects',
|
||||
'films.html',
|
||||
title='films',
|
||||
projects=projects._items,
|
||||
api=system_util.pillar_api())
|
||||
|
||||
@@ -261,12 +278,13 @@ def get_random_featured_nodes() -> typing.List[dict]:
|
||||
'summary': True,
|
||||
'picture_square': True}},
|
||||
{'$unwind': {'path': '$nodes_featured'}},
|
||||
{'$sample': {'size': 3}},
|
||||
{'$sample': {'size': 6}},
|
||||
{'$lookup': {'from': 'nodes',
|
||||
'localField': 'nodes_featured',
|
||||
'foreignField': '_id',
|
||||
'as': 'node'}},
|
||||
{'$unwind': {'path': '$node'}},
|
||||
{'$match': {'node._deleted': {'$ne': True}}},
|
||||
{'$project': {'url': True,
|
||||
'name': True,
|
||||
'summary': True,
|
||||
@@ -276,7 +294,11 @@ def get_random_featured_nodes() -> typing.List[dict]:
|
||||
'node.permissions': True,
|
||||
'node.picture': True,
|
||||
'node.properties.content_type': True,
|
||||
'node.properties.url': True}},
|
||||
'node.properties.duration_seconds': True,
|
||||
'node.properties.url': True,
|
||||
'node._created': True,
|
||||
}
|
||||
},
|
||||
])
|
||||
|
||||
featured_node_documents = []
|
||||
@@ -285,11 +307,10 @@ def get_random_featured_nodes() -> typing.List[dict]:
|
||||
# Turn the project-with-node doc into a node-with-project doc.
|
||||
node_document = node_info.pop('node')
|
||||
node_document['project'] = node_info
|
||||
node_document['_id'] = str(node_document['_id'])
|
||||
|
||||
node = Node(node_document)
|
||||
node.picture = get_file(node.picture, api=api)
|
||||
node.url = url_for_node(node=node)
|
||||
node.project.url = url_for('projects.view', project_url=node.project.url)
|
||||
node.project.picture_square = get_file(node.project.picture_square, api=api)
|
||||
featured_node_documents.append(node)
|
||||
|
||||
@@ -423,32 +444,27 @@ def emails_welcome_txt():
|
||||
return flask.Response(txt, content_type='text/plain; charset=utf-8')
|
||||
|
||||
|
||||
@blueprint.route('/nodes/<string(length=24):node_id>/comments')
|
||||
def comments_for_node(node_id):
|
||||
"""Overrides the default render_comments_for_node.
|
||||
@blueprint.route('/p/<project_url>')
|
||||
def project_landing(project_url):
|
||||
"""Override Pillar project_view endpoint completely.
|
||||
|
||||
This is done in order to extend can_post_comments by requiring the
|
||||
subscriber capability.
|
||||
The first part of the function is identical to the one in Pillar, but the
|
||||
second part (starting with 'Load custom project properties') extends the
|
||||
behaviour to support film project landing pages.
|
||||
"""
|
||||
|
||||
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()
|
||||
|
||||
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',
|
||||
project = find_project_or_404(project_url,
|
||||
embedded={'header_node': 1},
|
||||
api=api)
|
||||
|
||||
# Load the header video file, if there is any.
|
||||
header_video_file = None
|
||||
header_video_node = None
|
||||
@@ -458,15 +474,182 @@ def project_hero():
|
||||
header_video_file = get_file(project.header_node.properties.file)
|
||||
header_video_node.picture = get_file(header_video_node.picture)
|
||||
|
||||
pages = Node.all({
|
||||
'where': {'project': project._id, 'node_type': 'page'},
|
||||
'projection': {'name': 1}}, api=api)
|
||||
extra_context = {
|
||||
'header_video_file': header_video_file,
|
||||
'header_video_node': header_video_node}
|
||||
|
||||
# Load custom project properties. If the project has a 'cloud' extension prop,
|
||||
# render it using the projects/landing.html template and try to attach a
|
||||
# number of additional attributes (pages, images, etc.).
|
||||
if 'extension_props' in project and EXTENSION_NAME in project['extension_props']:
|
||||
extension_props = project['extension_props'][EXTENSION_NAME]
|
||||
extension_props['logo'] = get_file(extension_props['logo'])
|
||||
|
||||
pages = Node.all({
|
||||
'where': {
|
||||
'project': project._id,
|
||||
'node_type': 'page',
|
||||
'_deleted': {'$ne': True}},
|
||||
'projection': {'name': 1}}, api=api)
|
||||
|
||||
extra_context.update({'pages': pages._items})
|
||||
template_name = 'projects/landing.html'
|
||||
|
||||
return render_project(project, api,
|
||||
extra_context={'header_video_file': header_video_file,
|
||||
'header_video_node': header_video_node,
|
||||
'pages': pages._items,},
|
||||
template_name='projects/landing.html')
|
||||
extra_context=extra_context,
|
||||
template_name=template_name)
|
||||
|
||||
|
||||
@blueprint.route('/p/<project_url>/browse')
|
||||
@project_view()
|
||||
def project_browse(project: pillarsdk.Project):
|
||||
"""Project view displaying all top-level nodes.
|
||||
|
||||
We render a regular project view, but we introduce an additional template
|
||||
variable: browse. By doing that we prevent the regular project view
|
||||
from loading and fetch via AJAX a "group" node-like view instead (see
|
||||
project_browse_view_nodes).
|
||||
"""
|
||||
return render_template(
|
||||
'projects/view.html',
|
||||
api=system_util.pillar_api(),
|
||||
project=project,
|
||||
node=None,
|
||||
show_project=True,
|
||||
browse=True,
|
||||
og_picture=None,
|
||||
navigation_links=project_navigation_links(project, system_util.pillar_api()),
|
||||
extension_sidebar_links=current_app.extension_sidebar_links(project))
|
||||
|
||||
|
||||
@blueprint.route('/p/<project_url>/browse/nodes')
|
||||
@project_view()
|
||||
def project_browse_view_nodes(project: pillarsdk.Project):
|
||||
"""Display top-level nodes for a Project.
|
||||
|
||||
This view is always meant to be served embedded, as part of project_browse.
|
||||
"""
|
||||
api = system_util.pillar_api()
|
||||
# Get top level nodes
|
||||
projection = {
|
||||
'project': 1,
|
||||
'name': 1,
|
||||
'picture': 1,
|
||||
'node_type': 1,
|
||||
'properties.order': 1,
|
||||
'properties.status': 1,
|
||||
'user': 1,
|
||||
'properties.content_type': 1,
|
||||
'permissions.world': 1}
|
||||
where = {
|
||||
'project': project['_id'],
|
||||
'parent': {'$exists': False},
|
||||
'properties.status': 'published',
|
||||
'_deleted': {'$ne': True},
|
||||
'node_type': {'$in': ['group', 'asset']},
|
||||
}
|
||||
|
||||
try:
|
||||
nodes = Node.all({
|
||||
'projection': projection,
|
||||
'where': where,
|
||||
'sort': [('properties.order', 1), ('name', 1)]}, api=api)
|
||||
except pillarsdk.exceptions.ForbiddenAccess:
|
||||
return render_template('errors/403_embed.html')
|
||||
nodes = nodes._items
|
||||
|
||||
for child in nodes:
|
||||
child.picture = get_file(child.picture, api=api)
|
||||
return render_template(
|
||||
'projects/browse_embed.html',
|
||||
nodes=nodes)
|
||||
|
||||
|
||||
def project_settings(project: pillarsdk.Project, **template_args: dict):
|
||||
"""Renders the project settings page for Blender Cloud projects.
|
||||
|
||||
If the project has been setup for Blender Cloud, check for the cloud.category
|
||||
property, to render the proper form.
|
||||
"""
|
||||
|
||||
# Based on the project state, we can render a different template.
|
||||
if not current_cloud.is_cloud_project(project):
|
||||
return render_template('project_settings/offer_setup.html',
|
||||
project=project, **template_args)
|
||||
|
||||
cloud_props = project['extension_props'][EXTENSION_NAME]
|
||||
|
||||
category = cloud_props['category']
|
||||
if category != 'film':
|
||||
log.error('No interface available to edit %s projects, yet' % category)
|
||||
|
||||
form = FilmProjectForm()
|
||||
|
||||
# Iterate over the form fields and set the data if exists in the project document
|
||||
for field_name in form.data:
|
||||
if field_name not in cloud_props:
|
||||
continue
|
||||
# Skip csrf_token field
|
||||
if field_name == 'csrf_token':
|
||||
continue
|
||||
form_field = getattr(form, field_name)
|
||||
form_field.data = cloud_props[field_name]
|
||||
|
||||
return render_template('project_settings/settings.html',
|
||||
project=project,
|
||||
form=form,
|
||||
**template_args)
|
||||
|
||||
|
||||
@blueprint.route('/<project_url>/settings/film', methods=['POST'])
|
||||
@authorization.require_login(require_cap='admin')
|
||||
@project_view()
|
||||
def save_film_settings(project: pillarsdk.Project):
|
||||
# Ensure that the project is setup for Cloud (see @attract_project_view for example)
|
||||
form = FilmProjectForm()
|
||||
if not form.validate_on_submit():
|
||||
log.debug('Form submission failed')
|
||||
# Return list of validation errors
|
||||
|
||||
updated_extension_props = {}
|
||||
for field_name in form.data:
|
||||
# Skip csrf_token field
|
||||
if field_name == 'csrf_token':
|
||||
continue
|
||||
form_field = getattr(form, field_name)
|
||||
# TODO(fsiddi) if form_field type is FileSelectField, convert it to ObjectId
|
||||
# Currently this raises TypeError: Object of type 'ObjectId' is not JSON serializable
|
||||
|
||||
if form_field.data == '':
|
||||
form_field.data = None
|
||||
updated_extension_props[field_name] = form_field.data
|
||||
|
||||
# Update extension props and save project
|
||||
extension_props = project['extension_props'][EXTENSION_NAME]
|
||||
# Project is a Resource, so we update properties iteratively
|
||||
for k, v in updated_extension_props.items():
|
||||
extension_props[k] = v
|
||||
project.update(api=system_util.pillar_api())
|
||||
return '', 204
|
||||
|
||||
|
||||
@blueprint.route('/<project_url>/setup-for-film', methods=['POST'])
|
||||
@login_required
|
||||
@project_view()
|
||||
def setup_for_film(project: pillarsdk.Project):
|
||||
import cloud.setup
|
||||
|
||||
project_id = project._id
|
||||
|
||||
if not project.has_method('PUT'):
|
||||
log.warning('User %s tries to set up project %s for Blender Cloud, but has no PUT rights.',
|
||||
current_user, project_id)
|
||||
raise wz_exceptions.Forbidden()
|
||||
|
||||
log.info('User %s sets up project %s for Blender Cloud', current_user, project_id)
|
||||
cloud.setup.setup_for_film(project.url)
|
||||
|
||||
return '', 204
|
||||
|
||||
|
||||
def setup_app(app):
|
||||
|
54
cloud/setup.py
Normal file
@@ -0,0 +1,54 @@
|
||||
"""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},
|
||||
{'email': {'$in': [wh_payload['old_email'], email]}},
|
||||
]}
|
||||
db_users = users_coll.find(query)
|
||||
user_count = db_users.count()
|
||||
db_users = list(users_coll.find(query))
|
||||
user_count = len(db_users)
|
||||
if user_count > 1:
|
||||
# Now we have to pay the price for finding users in one query; we
|
||||
# have to prioritise them and return the one we think is most reliable.
|
||||
@@ -117,6 +117,10 @@ def insert_or_fetch_user(wh_payload: dict) -> typing.Optional[dict]:
|
||||
my_log.debug('found user %s', db_user['email'])
|
||||
return db_user
|
||||
|
||||
if wh_payload.get('date_deletion_requested'):
|
||||
my_log.info('Received update for a deleted user %s, not creating', bid_str)
|
||||
return None
|
||||
|
||||
# Pretend to create the user, so that we can inspect the resulting
|
||||
# capabilities. This is more future-proof than looking at the list
|
||||
# of roles in the webhook payload.
|
||||
@@ -164,6 +168,7 @@ def user_modified():
|
||||
'old_email': 'old@example.com',
|
||||
'full_name': 'Harry',
|
||||
'email': 'new@example'com,
|
||||
'avatar_changed': True,
|
||||
'roles': ['role1', 'role2', …]}
|
||||
"""
|
||||
my_log = log.getChild('user_modified')
|
||||
@@ -180,6 +185,10 @@ def user_modified():
|
||||
my_log.info('Received update for unknown user %r', payload['old_email'])
|
||||
return '', 204
|
||||
|
||||
if payload.get('date_deletion_requested'):
|
||||
delete_user(db_user, payload)
|
||||
return '', 204
|
||||
|
||||
# Use direct database updates to change the email and full name.
|
||||
# Also updates the db_user dict so that local_user below will have
|
||||
# the updated information.
|
||||
@@ -199,6 +208,11 @@ def user_modified():
|
||||
updates['full_name'] = db_user['username']
|
||||
db_user['full_name'] = updates['full_name']
|
||||
|
||||
if payload.get('avatar_changed'):
|
||||
import pillar.celery.avatar
|
||||
my_log.info('User %s changed avatar, scheduling download', db_user['_id'])
|
||||
pillar.celery.avatar.sync_avatar_for_user.delay(str(db_user['_id']))
|
||||
|
||||
if updates:
|
||||
users_coll = current_app.db('users')
|
||||
update_res = users_coll.update_one({'_id': db_user['_id']},
|
||||
@@ -213,3 +227,37 @@ def user_modified():
|
||||
subscription.do_update_subscription(local_user, payload)
|
||||
|
||||
return '', 204
|
||||
|
||||
|
||||
def delete_user(db_user, payload):
|
||||
"""Handle deletion request coming from BID."""
|
||||
my_log = log.getChild('delete_user')
|
||||
date_deletion_requested = payload['date_deletion_requested']
|
||||
bid_str = str(payload['id'])
|
||||
local_id = db_user['_id']
|
||||
my_log.info(
|
||||
'User %s with BID=%s requested deletion on %s, soft-deleting the user',
|
||||
local_id, bid_str, date_deletion_requested,
|
||||
)
|
||||
# Delete all session tokens linked to this user
|
||||
token_coll = current_app.db('tokens')
|
||||
delete_res = token_coll.delete_many({'user': local_id})
|
||||
my_log.info('Deleted %s session tokens of user %s', delete_res.deleted_count, local_id)
|
||||
|
||||
# Soft-delete the user and clear their PII
|
||||
users_coll = current_app.db('users')
|
||||
updates = {
|
||||
'_deleted': True,
|
||||
'email': None,
|
||||
'full_name': None,
|
||||
'username': None,
|
||||
'auth': [],
|
||||
}
|
||||
update_res = users_coll.update_one({'_id': local_id}, {'$set': updates})
|
||||
if update_res.matched_count != 1:
|
||||
my_log.error(
|
||||
'Soft-deleted %s users %s with BID=%s',
|
||||
update_res.matched_count, local_id, bid_str,
|
||||
)
|
||||
else:
|
||||
my_log.warning('Soft-deleted user %s with BID=%s', local_id, bid_str)
|
||||
|
@@ -30,3 +30,14 @@ URLER_SERVICE_AUTH_TOKEN = '##DEFINE##'
|
||||
ZENCODER_API_KEY = '##DEFINE##'
|
||||
ZENCODER_NOTIFICATIONS_SECRET = '##DEFINE##'
|
||||
ZENCODER_NOTIFICATIONS_URL = 'http://zencoderfetcher/'
|
||||
|
||||
# Special announcement on top of every page, for non-subscribers.
|
||||
# category: 'string', can be 'info', 'warning', 'danger', or 'success'.
|
||||
# message: 'string', any text, it gets markdowned.
|
||||
# icon: 'string', any icon in font-pillar. e.g. 'pi-heart-filled'
|
||||
UI_ANNOUNCEMENT_NON_SUBSCRIBERS = {
|
||||
'category': 'danger',
|
||||
'message': 'Spring will swing away the gray clouds, until then, '
|
||||
'[take cover under Blender Cloud](https://cloud.blender.org)!',
|
||||
'icon': 'pi-heart-filled',
|
||||
}
|
||||
|
@@ -1,6 +1,6 @@
|
||||
#!/bin/bash -e
|
||||
|
||||
DEPLOY_BRANCH=${DEPLOY_BRANCH:-production}
|
||||
STAGING_BRANCH=${STAGING_BRANCH:-production}
|
||||
|
||||
# macOS does not support readlink -f, so we use greadlink instead
|
||||
if [[ `uname` == 'Darwin' ]]; then
|
||||
@@ -11,34 +11,34 @@ else
|
||||
fi
|
||||
|
||||
ROOT="$(dirname "$(dirname "$($readlink -f "$0")")")"
|
||||
DEPLOYDIR="$ROOT/docker/4_run/deploy"
|
||||
STAGINGDIR="$ROOT/docker/4_run/staging"
|
||||
PROJECT_NAME="$(basename $ROOT)"
|
||||
|
||||
if [ -e $DEPLOYDIR ]; then
|
||||
echo "$DEPLOYDIR already exists, press [ENTER] to DESTROY AND DEPLOY, Ctrl+C to abort."
|
||||
if [ -e $STAGINGDIR ]; then
|
||||
echo "$STAGINGDIR already exists, press [ENTER] to destroy and re-install, Ctrl+C to abort."
|
||||
read dummy
|
||||
rm -rf $DEPLOYDIR
|
||||
rm -rf $STAGINGDIR
|
||||
else
|
||||
echo -n "Deploying to $DEPLOYDIR… "
|
||||
echo -n "Installing into $STAGINGDIR… "
|
||||
echo "press [ENTER] to continue, Ctrl+C to abort."
|
||||
read dummy
|
||||
fi
|
||||
|
||||
cd ${ROOT}
|
||||
mkdir -p $DEPLOYDIR
|
||||
REMOTE_ROOT="$DEPLOYDIR/$PROJECT_NAME"
|
||||
mkdir -p $STAGINGDIR
|
||||
REMOTE_ROOT="$STAGINGDIR/$PROJECT_NAME"
|
||||
|
||||
if [ -z "$SKIP_BRANCH_CHECK" ]; then
|
||||
# Check that we're on production branch.
|
||||
if [ $(git rev-parse --abbrev-ref HEAD) != "$DEPLOY_BRANCH" ]; then
|
||||
echo "You are NOT on the $DEPLOY_BRANCH branch, refusing to deploy." >&2
|
||||
if [ $(git rev-parse --abbrev-ref HEAD) != "$STAGING_BRANCH" ]; then
|
||||
echo "You are NOT on the $STAGING_BRANCH branch, refusing to stage." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check that production branch has been pushed.
|
||||
if [ -n "$(git log origin/$DEPLOY_BRANCH..$DEPLOY_BRANCH --oneline)" ]; then
|
||||
echo "WARNING: not all changes to the $DEPLOY_BRANCH branch have been pushed."
|
||||
echo "Press [ENTER] to continue deploying current origin/$DEPLOY_BRANCH, CTRL+C to abort."
|
||||
if [ -n "$(git log origin/$STAGING_BRANCH..$STAGING_BRANCH --oneline)" ]; then
|
||||
echo "WARNING: not all changes to the $STAGING_BRANCH branch have been pushed."
|
||||
echo "Press [ENTER] to continue staging current origin/$STAGING_BRANCH, CTRL+C to abort."
|
||||
read dummy
|
||||
fi
|
||||
fi
|
||||
@@ -88,15 +88,21 @@ function git_clone() {
|
||||
echo "==================================================================="
|
||||
echo "CLONING REPO ON $PROJECT_NAME @$BRANCH"
|
||||
URL=$(git -C $LOCAL_ROOT remote get-url origin)
|
||||
git -C $DEPLOYDIR clone --depth 1 --branch $BRANCH $URL $PROJECT_NAME
|
||||
git -C $STAGINGDIR clone --depth 1 --branch $BRANCH $URL $PROJECT_NAME
|
||||
}
|
||||
|
||||
git_clone pillar-python-sdk master $SDK_DIR
|
||||
git_clone pillar $DEPLOY_BRANCH $PILLAR_DIR
|
||||
git_clone attract $DEPLOY_BRANCH $ATTRACT_DIR
|
||||
git_clone flamenco $DEPLOY_BRANCH $FLAMENCO_DIR
|
||||
git_clone pillar-svnman $DEPLOY_BRANCH $SVNMAN_DIR
|
||||
git_clone blender-cloud $DEPLOY_BRANCH $ROOT
|
||||
if [ "$STAGING_BRANCH" == "production" ]; then
|
||||
SDK_STAGING_BRANCH=master # SDK doesn't have a production branch
|
||||
else
|
||||
SDK_STAGING_BRANCH=$STAGING_BRANCH
|
||||
fi
|
||||
|
||||
git_clone pillar-python-sdk $SDK_STAGING_BRANCH $SDK_DIR
|
||||
git_clone pillar $STAGING_BRANCH $PILLAR_DIR
|
||||
git_clone attract $STAGING_BRANCH $ATTRACT_DIR
|
||||
git_clone flamenco $STAGING_BRANCH $FLAMENCO_DIR
|
||||
git_clone pillar-svnman $STAGING_BRANCH $SVNMAN_DIR
|
||||
git_clone blender-cloud $STAGING_BRANCH $ROOT
|
||||
|
||||
# Gulp everywhere
|
||||
GULP=$ROOT/node_modules/.bin/gulp
|
||||
@@ -104,13 +110,21 @@ if [ ! -e $GULP -o gulpfile.js -nt $GULP ]; then
|
||||
npm install
|
||||
touch $GULP # installer doesn't always touch this after a build, so we do.
|
||||
fi
|
||||
$GULP --cwd $DEPLOYDIR/pillar --production
|
||||
$GULP --cwd $DEPLOYDIR/attract --production
|
||||
$GULP --cwd $DEPLOYDIR/flamenco --production
|
||||
$GULP --cwd $DEPLOYDIR/pillar-svnman --production
|
||||
$GULP --cwd $DEPLOYDIR/blender-cloud --production
|
||||
|
||||
# List of projects
|
||||
PROJECTS="pillar attract flamenco pillar-svnman blender-cloud"
|
||||
|
||||
# Run ./gulp for every project
|
||||
for PROJECT in $PROJECTS; do
|
||||
pushd $STAGINGDIR/$PROJECT; ./gulp --production; popd;
|
||||
done
|
||||
|
||||
# Remove node_modules (only after all projects with interdependencies have been built)
|
||||
for PROJECT in $PROJECTS; do
|
||||
pushd $STAGINGDIR/$PROJECT; rm -r node_modules; popd;
|
||||
done
|
||||
|
||||
echo
|
||||
echo "==================================================================="
|
||||
echo "Deploy of ${PROJECT_NAME} is ready for dockerisation."
|
||||
echo "Staging of ${PROJECT_NAME} is ready for dockerisation."
|
||||
echo "==================================================================="
|
||||
|
@@ -9,7 +9,6 @@ else
|
||||
fi
|
||||
ROOT="$(dirname "$(dirname "$($readlink -f "$0")")")"
|
||||
PROJECT_NAME="$(basename $ROOT)"
|
||||
DOCKER_DEPLOYDIR="$ROOT/docker/4_run/deploy"
|
||||
DOCKER_IMAGE="armadillica/blender_cloud:latest"
|
||||
REMOTE_SECRET_CONFIG_DIR="/data/config"
|
||||
REMOTE_DOCKER_COMPOSE_DIR="/root/docker"
|
||||
|
39
deploy/README.md
Normal file
@@ -0,0 +1,39 @@
|
||||
# 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 +0,0 @@
|
||||
build-quick.sh
|
43
deploy/build-all.sh
Executable file
@@ -0,0 +1,43 @@
|
||||
#!/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-pull.sh
Symbolic link
@@ -0,0 +1 @@
|
||||
build-quick.sh
|
@@ -1,34 +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")")")"
|
||||
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
deploy/build-quick.sh
Symbolic link
@@ -0,0 +1 @@
|
||||
build-all.sh
|
9
deploy/full-all.sh
Executable file
@@ -0,0 +1,9 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
NAME="$(basename "$0")"
|
||||
|
||||
./2docker.sh
|
||||
./${NAME/full-/build-}
|
||||
./2server.sh cloud2
|
1
deploy/full-pull.sh
Symbolic link
@@ -0,0 +1 @@
|
||||
full-all.sh
|
1
deploy/full-quick.sh
Symbolic link
@@ -0,0 +1 @@
|
||||
full-all.sh
|
@@ -1,5 +1,7 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
DOCKER_IMAGE_NAME=armadillica/pillar_wheelbuilder
|
||||
|
||||
set -e
|
||||
|
||||
# macOS does not support readlink -f, so we use greadlink instead
|
||||
@@ -21,25 +23,40 @@ fi
|
||||
|
||||
echo "Wheelhouse is $WHEELHOUSE"
|
||||
mkdir -p "$WHEELHOUSE"
|
||||
rm -f "$WHEELHOUSE"/*
|
||||
|
||||
docker build -t pillar_wheelbuilder .
|
||||
docker build -t $DOCKER_IMAGE_NAME:latest .
|
||||
|
||||
GID=$(id -g)
|
||||
docker run --rm -i \
|
||||
-v "$WHEELHOUSE:/data/wheelhouse" \
|
||||
-v "$TOPDEVDIR:/data/topdev" \
|
||||
pillar_wheelbuilder <<EOT
|
||||
$DOCKER_IMAGE_NAME <<EOT
|
||||
set -e
|
||||
set -x
|
||||
|
||||
# Globally upgrade Pip, so that we can get a compatible version of the cryptography package.
|
||||
# See https://github.com/pyca/cryptography/issues/5771
|
||||
pip3 install --upgrade pip setuptools wheel
|
||||
|
||||
# Pin poetry to 1.0, as more recent version to not support nested filesystem package
|
||||
# dependencies.
|
||||
pip3 install wheel poetry==1.0 cryptography==2.7
|
||||
|
||||
# Build wheels for all dependencies.
|
||||
cd /data/topdev/blender-cloud
|
||||
pip3 install wheel
|
||||
pip3 wheel --wheel-dir=/data/wheelhouse -r requirements.txt
|
||||
chown -R $UID:$GID /data/wheelhouse
|
||||
|
||||
# Install the dependencies so that we can get a full freeze.
|
||||
pip3 install --no-index --find-links=/data/wheelhouse -r requirements.txt
|
||||
pip3 freeze | grep -v '^-[ef] ' > /data/wheelhouse/requirements.txt
|
||||
poetry install --no-dev
|
||||
|
||||
# Apparently pip doesn't like projects without setup.py, so it think we have 'pillar-svnman' as
|
||||
# requirement (because that's the name of the directory). We have to grep that out.
|
||||
poetry run pip3 freeze | grep -v '\(pillar\)\|\(^-[ef] \)' > \$WHEELHOUSE/requirements.txt
|
||||
|
||||
pip3 wheel --wheel-dir=\$WHEELHOUSE -r \$WHEELHOUSE/requirements.txt
|
||||
chown -R $UID:$GID \$WHEELHOUSE
|
||||
EOT
|
||||
|
||||
# Remove our own projects, they shouldn't be installed as wheel (for now).
|
||||
rm -f $WHEELHOUSE/{attract,flamenco,pillar,pillarsdk}*.whl
|
||||
|
||||
echo "Build of $DOCKER_IMAGE_NAME:latest is done."
|
||||
|
@@ -38,8 +38,9 @@ ENV USE_X_SENDFILE True
|
||||
EXPOSE 80
|
||||
EXPOSE 5000
|
||||
|
||||
ADD apache/remoteip.conf /etc/apache2/mods-available/
|
||||
ADD apache/wsgi-py36.* /etc/apache2/mods-available/
|
||||
RUN a2enmod rewrite && a2enmod wsgi-py36
|
||||
RUN a2enmod remoteip && a2enmod rewrite && a2enmod wsgi-py36
|
||||
|
||||
ADD apache/apache2.conf /etc/apache2/apache2.conf
|
||||
ADD apache/000-default.conf /etc/apache2/sites-available/000-default.conf
|
||||
@@ -57,7 +58,7 @@ ENTRYPOINT /docker-entrypoint.sh
|
||||
|
||||
# Add the most-changing files as last step for faster rebuilds.
|
||||
ADD config_local.py /data/git/blender-cloud/
|
||||
ADD deploy /data/git
|
||||
ADD staging /data/git
|
||||
RUN python3 -c "import re, secrets; \
|
||||
f = open('/data/git/blender-cloud/config_local.py', 'a'); \
|
||||
h = re.sub(r'[_.~-]', '', secrets.token_urlsafe())[:8]; \
|
||||
|
@@ -49,6 +49,8 @@
|
||||
RewriteRule "^/training/?$" "/courses" [R=301,L]
|
||||
RewriteRule "^/spring/?$" "/p/spring" [R=301,L]
|
||||
RewriteRule "^/hero/?$" "/p/hero" [R=301,L]
|
||||
RewriteRule "^/coffee-run/?$" "/p/coffee-run" [R=301,L]
|
||||
RewriteRule "^/settlers/?$" "/p/settlers" [R=301,L]
|
||||
# Waking the forest was moved from the art gallery to its own workshop
|
||||
RewriteRule "^/p/gallery/58cfec4f88ac8f1440aeb309/?$" "/p/waking-the-forest" [R=301,L]
|
||||
</VirtualHost>
|
||||
|
@@ -133,9 +133,9 @@ AccessFileName .htaccess
|
||||
# Note that the use of %{X-Forwarded-For}i instead of %h is not recommended.
|
||||
# Use mod_remoteip instead.
|
||||
#
|
||||
LogFormat "%v:%p %h %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\"" vhost_combined
|
||||
LogFormat "%h %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\"" combined
|
||||
LogFormat "%h %l %u %t \"%r\" %>s %O" common
|
||||
LogFormat "%v:%p %a %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\"" vhost_combined
|
||||
LogFormat "%a %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\"" combined
|
||||
LogFormat "%a %l %u %t \"%r\" %>s %O" common
|
||||
LogFormat "%{Referer}i -> %U" referer
|
||||
LogFormat "%{User-agent}i" agent
|
||||
|
||||
|
2
docker/4_run/apache/remoteip.conf
Normal file
@@ -0,0 +1,2 @@
|
||||
RemoteIPHeader X-Forwarded-For
|
||||
RemoteIPInternalProxy 172.16.0.0/12
|
@@ -106,15 +106,6 @@ UTM_LINKS = {
|
||||
}
|
||||
}
|
||||
|
||||
# Disabled until we have regenerated the majority of the links.
|
||||
CELERY_BEAT_SCHEDULE = {
|
||||
'regenerate-expired-links': {
|
||||
'task': 'pillar.celery.file_link_tasks.regenerate_all_expired_links',
|
||||
'schedule': 600, # every N seconds
|
||||
'args': ('gcs', 500)
|
||||
},
|
||||
}
|
||||
|
||||
SVNMAN_REPO_URL = 'https://svn.blender.cloud/repo/'
|
||||
SVNMAN_API_URL = 'https://svn.blender.cloud/api/'
|
||||
|
||||
|
@@ -1,7 +1,7 @@
|
||||
version: '3.4'
|
||||
services:
|
||||
mongo:
|
||||
image: mongo:3.4.2
|
||||
image: mongo:3.4
|
||||
container_name: mongo
|
||||
restart: always
|
||||
volumes:
|
||||
@@ -15,8 +15,12 @@ services:
|
||||
max-size: "200k"
|
||||
max-file: "20"
|
||||
|
||||
# Databases in use:
|
||||
# 0: Flask Cache
|
||||
# 1: Celery (backend)
|
||||
# 2: Celery (broker)
|
||||
redis:
|
||||
image: redis:3.2.8
|
||||
image: redis:5.0
|
||||
container_name: redis
|
||||
restart: always
|
||||
ports:
|
||||
@@ -27,19 +31,8 @@ services:
|
||||
max-size: "200k"
|
||||
max-file: "20"
|
||||
|
||||
rabbit:
|
||||
image: rabbitmq:3.6.10
|
||||
container_name: rabbit
|
||||
restart: always
|
||||
ports:
|
||||
- "127.0.0.1:5672:5672"
|
||||
logging:
|
||||
driver: "json-file"
|
||||
options:
|
||||
max-size: "200k"
|
||||
max-file: "20"
|
||||
|
||||
elastic:
|
||||
# This image is defined in blender-cloud/docker/elastic
|
||||
image: armadillica/elasticsearch:6.1.1
|
||||
container_name: elastic
|
||||
restart: always
|
||||
@@ -70,6 +63,7 @@ services:
|
||||
max-file: "20"
|
||||
|
||||
kibana:
|
||||
# This image is defined in blender-cloud/docker/elastic
|
||||
image: armadillica/kibana:6.1.1
|
||||
container_name: kibana
|
||||
restart: always
|
||||
@@ -109,7 +103,6 @@ services:
|
||||
depends_on:
|
||||
- mongo
|
||||
- redis
|
||||
- rabbit
|
||||
|
||||
celery_worker:
|
||||
image: armadillica/blender_cloud:latest
|
||||
@@ -126,7 +119,6 @@ services:
|
||||
depends_on:
|
||||
- mongo
|
||||
- redis
|
||||
- rabbit
|
||||
logging:
|
||||
driver: "json-file"
|
||||
options:
|
||||
@@ -148,7 +140,6 @@ services:
|
||||
depends_on:
|
||||
- mongo
|
||||
- redis
|
||||
- rabbit
|
||||
- celery_worker
|
||||
logging:
|
||||
driver: "json-file"
|
||||
@@ -169,7 +160,8 @@ services:
|
||||
- /data/letsencrypt:/data/letsencrypt
|
||||
|
||||
haproxy:
|
||||
image: dockercloud/haproxy:1.5.3
|
||||
# This image is defined in blender-cloud/docker/haproxy
|
||||
image: armadillica/haproxy:1.6.7
|
||||
container_name: haproxy
|
||||
restart: always
|
||||
ports:
|
||||
|
5
docker/haproxy/Dockerfile
Normal file
@@ -0,0 +1,5 @@
|
||||
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
|
10
docker/haproxy/build.sh
Executable file
@@ -0,0 +1,10 @@
|
||||
#!/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,9 +15,8 @@ if [ "$1" == "watch" ]; then
|
||||
# Treat "gulp watch" as "gulp && gulp watch"
|
||||
$GULP
|
||||
elif [ "$1" == "all" ]; then
|
||||
pushd .
|
||||
# This is useful when building the Blender Cloud project for the first time.
|
||||
# Run "gulp" once inside the repo
|
||||
$GULP
|
||||
# Run ./gulp in all depending projects (pillar, attract, flamenco, pillar-svnman)
|
||||
declare -a repos=("pillar" "attract" "flamenco" "pillar-svnman")
|
||||
for r in "${repos[@]}"
|
||||
@@ -25,6 +24,9 @@ elif [ "$1" == "all" ]; then
|
||||
cd ../$r
|
||||
./gulp
|
||||
done
|
||||
popd
|
||||
# Run "gulp" once inside the repo
|
||||
$GULP
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
96
gulpfile.js
@@ -1,20 +1,19 @@
|
||||
var argv = require('minimist')(process.argv.slice(2));
|
||||
var autoprefixer = require('gulp-autoprefixer');
|
||||
var cache = require('gulp-cached');
|
||||
var chmod = require('gulp-chmod');
|
||||
var concat = require('gulp-concat');
|
||||
var git = require('gulp-git');
|
||||
var gulp = require('gulp');
|
||||
var gulpif = require('gulp-if');
|
||||
var pug = require('gulp-pug');
|
||||
var livereload = require('gulp-livereload');
|
||||
var plumber = require('gulp-plumber');
|
||||
var rename = require('gulp-rename');
|
||||
var sass = require('gulp-sass');
|
||||
var sourcemaps = require('gulp-sourcemaps');
|
||||
var uglify = require('gulp-uglify-es').default;
|
||||
let argv = require('minimist')(process.argv.slice(2));
|
||||
let autoprefixer = require('gulp-autoprefixer');
|
||||
let cache = require('gulp-cached');
|
||||
let chmod = require('gulp-chmod');
|
||||
let concat = require('gulp-concat');
|
||||
let git = require('gulp-git');
|
||||
let gulp = require('gulp');
|
||||
let gulpif = require('gulp-if');
|
||||
let pug = require('gulp-pug');
|
||||
let plumber = require('gulp-plumber');
|
||||
let rename = require('gulp-rename');
|
||||
let sass = require('gulp-sass');
|
||||
let sourcemaps = require('gulp-sourcemaps');
|
||||
let uglify = require('gulp-uglify-es').default;
|
||||
|
||||
var enabled = {
|
||||
let enabled = {
|
||||
uglify: argv.production,
|
||||
maps: !argv.production,
|
||||
failCheck: !argv.production,
|
||||
@@ -24,19 +23,19 @@ var enabled = {
|
||||
chmod: argv.production,
|
||||
};
|
||||
|
||||
var destination = {
|
||||
let destination = {
|
||||
css: 'cloud/static/assets/css',
|
||||
pug: 'cloud/templates',
|
||||
js: 'cloud/static/assets/js',
|
||||
}
|
||||
|
||||
var source = {
|
||||
let source = {
|
||||
pillar: '../pillar/'
|
||||
}
|
||||
|
||||
|
||||
/* CSS */
|
||||
gulp.task('styles', function() {
|
||||
gulp.task('styles', function(done) {
|
||||
gulp.src('src/styles/**/*.sass')
|
||||
.pipe(gulpif(enabled.failCheck, plumber()))
|
||||
.pipe(gulpif(enabled.maps, sourcemaps.init()))
|
||||
@@ -45,27 +44,26 @@ gulp.task('styles', function() {
|
||||
))
|
||||
.pipe(autoprefixer("last 3 versions"))
|
||||
.pipe(gulpif(enabled.maps, sourcemaps.write(".")))
|
||||
.pipe(gulp.dest(destination.css))
|
||||
.pipe(gulpif(argv.livereload, livereload()));
|
||||
.pipe(gulp.dest(destination.css));
|
||||
done();
|
||||
});
|
||||
|
||||
|
||||
/* Templates - Pug */
|
||||
gulp.task('templates', function() {
|
||||
gulp.task('templates', function(done) {
|
||||
gulp.src('src/templates/**/*.pug')
|
||||
.pipe(gulpif(enabled.failCheck, plumber()))
|
||||
.pipe(gulpif(enabled.cachify, cache('templating')))
|
||||
.pipe(pug({
|
||||
pretty: enabled.prettyPug
|
||||
}))
|
||||
.pipe(gulp.dest(destination.pug))
|
||||
.pipe(gulpif(argv.livereload, livereload()));
|
||||
.pipe(gulp.dest(destination.pug));
|
||||
// TODO(venomgfx): please check why 'gulp watch' doesn't pick up on .txt changes.
|
||||
gulp.src('src/templates/**/*.txt')
|
||||
.pipe(gulpif(enabled.failCheck, plumber()))
|
||||
.pipe(gulpif(enabled.cachify, cache('templating')))
|
||||
.pipe(gulp.dest(destination.pug))
|
||||
.pipe(gulpif(argv.livereload, livereload()));
|
||||
.pipe(gulp.dest(destination.pug));
|
||||
done();
|
||||
});
|
||||
|
||||
|
||||
@@ -73,7 +71,7 @@ gulp.task('templates', function() {
|
||||
|
||||
|
||||
/* Individual Uglified Scripts */
|
||||
gulp.task('scripts', function() {
|
||||
gulp.task('scripts', function(done) {
|
||||
gulp.src('src/scripts/*.js')
|
||||
.pipe(gulpif(enabled.failCheck, plumber()))
|
||||
.pipe(gulpif(enabled.cachify, cache('scripting')))
|
||||
@@ -81,29 +79,39 @@ gulp.task('scripts', function() {
|
||||
.pipe(gulpif(enabled.uglify, uglify()))
|
||||
.pipe(rename({suffix: '.min'}))
|
||||
.pipe(gulpif(enabled.maps, sourcemaps.write(".")))
|
||||
.pipe(gulpif(enabled.chmod, chmod(644)))
|
||||
.pipe(gulp.dest(destination.js))
|
||||
.pipe(gulpif(argv.livereload, livereload()));
|
||||
.pipe(gulpif(enabled.chmod, chmod(0o644)))
|
||||
.pipe(gulp.dest(destination.js));
|
||||
done();
|
||||
});
|
||||
|
||||
|
||||
// While developing, run 'gulp watch'
|
||||
gulp.task('watch',function() {
|
||||
// Only listen for live reloads if ran with --livereload
|
||||
if (argv.livereload){
|
||||
livereload.listen();
|
||||
}
|
||||
gulp.task('watch',function(done) {
|
||||
let watchStyles = [
|
||||
'src/styles/**/*.sass',
|
||||
source.pillar + 'src/styles/**/*.sass',
|
||||
];
|
||||
|
||||
gulp.watch('src/styles/**/*.sass',['styles']);
|
||||
gulp.watch(source.pillar + 'src/styles/**/*.sass',['styles']);
|
||||
gulp.watch('src/scripts/*.js',['scripts']);
|
||||
gulp.watch('src/templates/**/*.pug',['templates']);
|
||||
let watchScripts = [
|
||||
'src/scripts/**/*.js',
|
||||
source.pillar + 'src/scripts/**/*.js',
|
||||
];
|
||||
|
||||
let watchTemplates = [
|
||||
'src/templates/**/*.pug',
|
||||
source.pillar + 'src/templates/**/*.pug',
|
||||
];
|
||||
|
||||
gulp.watch(watchStyles, gulp.series('styles'));
|
||||
gulp.watch(watchScripts, gulp.series('scripts'));
|
||||
gulp.watch(watchTemplates, gulp.series('templates'));
|
||||
done();
|
||||
});
|
||||
|
||||
|
||||
// Erases all generated files in output directories.
|
||||
gulp.task('cleanup', function() {
|
||||
var paths = [];
|
||||
gulp.task('cleanup', function(done) {
|
||||
let paths = [];
|
||||
for (attr in destination) {
|
||||
paths.push(destination[attr]);
|
||||
}
|
||||
@@ -111,12 +119,12 @@ gulp.task('cleanup', function() {
|
||||
git.clean({ args: '-f -X ' + paths.join(' ') }, function (err) {
|
||||
if(err) throw err;
|
||||
});
|
||||
|
||||
done();
|
||||
});
|
||||
|
||||
|
||||
// Run 'gulp' to build everything at once
|
||||
var tasks = [];
|
||||
let tasks = [];
|
||||
if (enabled.cleanup) tasks.push('cleanup');
|
||||
|
||||
gulp.task('default', tasks.concat(['styles', 'templates', 'scripts']));
|
||||
gulp.task('default', gulp.parallel(tasks.concat(['styles', 'templates', 'scripts'])));
|
||||
|
6
jwtkeys/README.md
Normal file
@@ -0,0 +1,6 @@
|
||||
# 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
|
3824
package-lock.json
generated
@@ -7,18 +7,17 @@
|
||||
"url": "git://git.blender.org/blender-cloud.git"
|
||||
},
|
||||
"devDependencies": {
|
||||
"gulp": "~3.9.1",
|
||||
"gulp": "~4.0",
|
||||
"gulp-autoprefixer": "~6.0.0",
|
||||
"gulp-cached": "~1.1.1",
|
||||
"gulp-chmod": "~2.0.0",
|
||||
"gulp-concat": "~2.6.1",
|
||||
"gulp-if": "^2.0.2",
|
||||
"gulp-git": "~2.8.0",
|
||||
"gulp-livereload": "~4.0.0",
|
||||
"gulp-plumber": "~1.2.0",
|
||||
"gulp-pug": "~4.0.1",
|
||||
"gulp-rename": "~1.4.0",
|
||||
"gulp-sass": "~4.0.1",
|
||||
"gulp-sass": "~4.1.0",
|
||||
"gulp-sourcemaps": "~2.6.4",
|
||||
"gulp-uglify-es": "^1.0.4",
|
||||
"minimist": "^1.2.0"
|
||||
@@ -26,6 +25,7 @@
|
||||
"dependencies": {
|
||||
"bootstrap": "^4.1.3",
|
||||
"jquery": "^3.3.1",
|
||||
"natives": "^1.1.6",
|
||||
"popper.js": "^1.14.4",
|
||||
"video.js": "^7.2.2"
|
||||
}
|
||||
|
1917
poetry.lock
generated
Normal file
2
poetry.toml
Normal file
@@ -0,0 +1,2 @@
|
||||
[virtualenvs]
|
||||
in-project = false
|
75
pyproject.toml
Normal file
@@ -0,0 +1,75 @@
|
||||
[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"
|
@@ -1,12 +0,0 @@
|
||||
-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 .
|
@@ -1,4 +0,0 @@
|
||||
-r ../pillar/requirements.txt
|
||||
-r ../attract/requirements.txt
|
||||
-r ../flamenco/requirements.txt
|
||||
-r ../pillar-svnman/requirements.txt
|
18
setup.py
@@ -1,18 +0,0 @@
|
||||
#!/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,143 +2,27 @@
|
||||
* Support for fetching & rendering assets by tags.
|
||||
*/
|
||||
(function($) {
|
||||
/* How many nodes to load initially, and when clicked on the 'Load Next' link. */
|
||||
const LOAD_INITIAL_COUNT = 5;
|
||||
const LOAD_NEXT_COUNT = 3;
|
||||
|
||||
/* Renders a node as an asset card, returns a jQuery object. */
|
||||
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)
|
||||
$.fn.loadTaggedAssets = function(load_initial_count, load_next_count, has_subscription) {
|
||||
mark_if_public = !has_subscription;
|
||||
this.each(function(index, each) {
|
||||
let $card_deck_element = $(each)
|
||||
$card_deck_element.trigger('pillar:workStart');
|
||||
$.get('/api/nodes/tagged/' + $card_deck_element.data('assetTag'))
|
||||
.fail(function(error) {
|
||||
let msg = xhrErrorResponseMessage(error);
|
||||
console.log(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);
|
||||
$card_deck_element
|
||||
.append(
|
||||
$('<p>').addClass('bg-danger').text(msg)
|
||||
);
|
||||
})
|
||||
.done(function(resp) {
|
||||
// 'resp' is a list of node documents.
|
||||
// Store the response on the DOM card_deck_element so that we can later render more.
|
||||
card_deck_element.tagged_assets = resp;
|
||||
|
||||
// Here render the first N.
|
||||
for (node of resp.slice(0, LOAD_INITIAL_COUNT)) {
|
||||
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);
|
||||
}
|
||||
$card_deck_element.append(
|
||||
pillar.templates.Nodes.createListOf$nodeItems(resp, load_initial_count, load_next_count)
|
||||
);
|
||||
})
|
||||
.always(function() {
|
||||
$card_deck_element.trigger('pillar:workStop');
|
||||
});
|
||||
});
|
||||
};
|
||||
|
@@ -1,11 +1,60 @@
|
||||
body.homepage
|
||||
.blog
|
||||
.jumbotron
|
||||
padding-top: 6em
|
||||
padding-bottom: 6em
|
||||
.random-featured
|
||||
// Hide irrelevant info from cards.
|
||||
li
|
||||
&.item-type,
|
||||
&.item-date
|
||||
@extend .d-none
|
||||
|
||||
*
|
||||
font-size: $h1-font-size
|
||||
// Fit 3 cards per row.
|
||||
&.card-deck.card-deck-responsive
|
||||
.card
|
||||
+media-md
|
||||
flex: 1 0 30%
|
||||
max-width: 30%
|
||||
flex: 1 0 31%
|
||||
max-width: 31%
|
||||
|
||||
.lead
|
||||
font-size: $font-size-base
|
||||
.homepage
|
||||
.timeline
|
||||
.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
|
||||
|
37
src/styles/_list_films.sass
Normal file
@@ -0,0 +1,37 @@
|
||||
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,141 +1,36 @@
|
||||
$node-latest-thumbnail-size: 160px
|
||||
$node-latest-gallery-thumbnail-size: 200px
|
||||
.landing-home
|
||||
.page-content
|
||||
@extend .text-white
|
||||
background-color: $color-bg-dark-pages
|
||||
|
||||
.jumbotron
|
||||
+media-xs
|
||||
background-position: top right
|
||||
background-position: top center
|
||||
padding-bottom: 16em
|
||||
padding-top: 8em
|
||||
|
||||
.card
|
||||
@extend .bg-transparent
|
||||
@extend .text-white
|
||||
|
||||
.card-text
|
||||
@extend .text-secondary
|
||||
|
||||
.landing
|
||||
.node-details-description
|
||||
iframe
|
||||
max-width: 100%
|
||||
@extend .mx-auto
|
||||
color: #ddd
|
||||
font-size: 1.3em
|
||||
|
||||
.node-extra
|
||||
display: flex
|
||||
flex-direction: column
|
||||
width: 100%
|
||||
a
|
||||
color: $color-primary
|
||||
|
||||
.node-updates
|
||||
flex: 1
|
||||
font-size: 1.1em
|
||||
|
||||
ul
|
||||
padding: 0
|
||||
margin: 0 0 15px 0
|
||||
display: flex
|
||||
flex-direction: row
|
||||
flex-wrap: wrap
|
||||
|
||||
li
|
||||
display: flex
|
||||
flex-direction: column
|
||||
list-style: none
|
||||
padding: 5px
|
||||
cursor: pointer
|
||||
width: 33.3333%
|
||||
|
||||
+media-xs
|
||||
width: 100%
|
||||
|
||||
&.texture, &.group_texture
|
||||
width: 25%
|
||||
|
||||
&:hover
|
||||
img
|
||||
opacity: .9
|
||||
a.title
|
||||
//color: $color-primary
|
||||
text-decoration: underline
|
||||
|
||||
&.post
|
||||
.info .title
|
||||
//color: $node-type-post
|
||||
font-size: 1.1em
|
||||
a.image
|
||||
border: none
|
||||
//border-color: $node-type-post
|
||||
background-color: hsl(hue($node-type-post), 20%, 55%)
|
||||
|
||||
&.asset.image a.image
|
||||
border-color: $node-type-asset_image
|
||||
background-color: hsl(hue($node-type-asset_image), 20%, 55%)
|
||||
&.asset.file a.image
|
||||
border-color: $node-type-asset_file
|
||||
background-color: hsl(hue($node-type-asset_file), 20%, 55%)
|
||||
&.asset.video a.image
|
||||
border-color: $node-type-asset_video
|
||||
background-color: hsl(hue($node-type-asset_video), 20%, 55%)
|
||||
|
||||
.image
|
||||
width: 100%
|
||||
height: $node-latest-thumbnail-size
|
||||
min-height: $node-latest-thumbnail-size
|
||||
max-height: $node-latest-thumbnail-size
|
||||
background-color: $color-background
|
||||
margin: 5px auto 10px auto
|
||||
position: relative
|
||||
overflow: hidden
|
||||
border-radius: 0
|
||||
|
||||
img
|
||||
max-height: $node-latest-thumbnail-size
|
||||
+position-center-translate
|
||||
|
||||
i
|
||||
color: rgba(white, .9)
|
||||
font-size: 1.8em
|
||||
position: absolute
|
||||
bottom: 3px
|
||||
left: 5px
|
||||
text-shadow: 1px 1px 0 rgba(black, .2)
|
||||
|
||||
&.pi-file-archive
|
||||
font-size: 1.5em
|
||||
bottom: 5px
|
||||
&.pi-newspaper
|
||||
font-size: 1.6em
|
||||
left: 7px
|
||||
|
||||
.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)
|
||||
.btn-outline-primary
|
||||
border-color: $color-text-light
|
||||
color: $color-text-light
|
||||
|
||||
&:hover
|
||||
border-color: transparent
|
||||
|
||||
.gallery
|
||||
max-width: 1024px
|
||||
@@ -147,9 +42,10 @@ $node-latest-gallery-thumbnail-size: 200px
|
||||
padding-bottom: 23%
|
||||
margin: 0.83%
|
||||
overflow: hidden
|
||||
transition: box-shadow 150ms ease-in-out
|
||||
|
||||
&:hover
|
||||
box-shadow: 2px 2px 50px 0 rgba(0,0,0,0.3)
|
||||
box-shadow: 2px 6px 50px 0 rgba(black, .2)
|
||||
|
||||
.img-container
|
||||
position: absolute
|
||||
@@ -160,45 +56,12 @@ $node-latest-gallery-thumbnail-size: 200px
|
||||
width: 300%
|
||||
transform: translate(-20%,-10%)
|
||||
|
||||
&:hover .img-caption
|
||||
top: 0
|
||||
left: 0
|
||||
.btn-trans
|
||||
background: rgba(255,255,255,0.4)
|
||||
|
||||
.img-caption
|
||||
position: absolute
|
||||
width: 100%
|
||||
height: 100%
|
||||
background: rgba(0, 0, 0, 0.3)
|
||||
text-align: center
|
||||
|
||||
.table
|
||||
display: table
|
||||
.table-cell
|
||||
display: table-cell
|
||||
vertical-align: bottom
|
||||
border: none
|
||||
|
||||
@media screen and (max-width: 992px)
|
||||
.thumbnail
|
||||
width: 22%
|
||||
padding-bottom: 22%
|
||||
margin: 1.5%
|
||||
|
||||
.img-container:hover .img-caption
|
||||
top: 0
|
||||
left: 0
|
||||
|
||||
.img-caption
|
||||
position: absolute
|
||||
width: 100%
|
||||
height: 100%
|
||||
background: rgba(0, 0, 0, .7)
|
||||
text-align: center
|
||||
a
|
||||
color: $yellow
|
||||
|
||||
@media screen and (max-width: 720px)
|
||||
.thumbnail
|
||||
width: 29%
|
||||
@@ -210,3 +73,13 @@ $node-latest-gallery-thumbnail-size: 200px
|
||||
width: 44%
|
||||
padding-bottom: 44%
|
||||
margin: 3%
|
||||
|
||||
.jumbotron
|
||||
&.jumbotron-overlay-gradient-fade-to-gray
|
||||
*
|
||||
z-index: 1
|
||||
&:after
|
||||
background-color: transparent
|
||||
background-image: linear-gradient(transparent 60%, $color-bg-dark-pages 100%)
|
||||
display: block
|
||||
visibility: visible
|
||||
|
4
src/styles/_utils.sass
Normal file
@@ -0,0 +1,4 @@
|
||||
.list-first-new
|
||||
li:first-child
|
||||
span
|
||||
@extend .new
|
5
src/styles/_variables.sass
Normal file
@@ -0,0 +1,5 @@
|
||||
$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)
|
||||
|
||||
a.sign-up-now
|
||||
+button($color-primary, 3px, true)
|
||||
+button($color-primary, $btn-border-radius, true)
|
||||
|
||||
h3
|
||||
font-size: 1.8em
|
||||
@@ -361,7 +361,7 @@ section.pricing
|
||||
transform: translateX(-50%)
|
||||
font-size: 1.2em
|
||||
|
||||
+button($color-primary, 3px)
|
||||
+button($color-primary, $btn-border-radius)
|
||||
padding: 5px 25px
|
||||
white-space: nowrap
|
||||
text-align: center
|
||||
|
@@ -7,6 +7,9 @@
|
||||
@import "../../../pillar/src/styles/config"
|
||||
@import "../../../pillar/src/styles/utils"
|
||||
|
||||
$pillar-font-path: "../../../../static/pillar/assets/font"
|
||||
@import "../../../pillar/src/styles/font-pillar"
|
||||
|
||||
// Bootstrap components.
|
||||
@import "../../../pillar/node_modules/bootstrap/scss/root"
|
||||
@import "../../../pillar/node_modules/bootstrap/scss/reboot"
|
||||
@@ -71,6 +74,8 @@
|
||||
@import "../../../pillar/src/styles/components/checkbox"
|
||||
@import "../../../pillar/src/styles/components/overlay"
|
||||
@import "../../../pillar/src/styles/components/card"
|
||||
@import "../../../pillar/src/styles/components/placeholder"
|
||||
@import "../../../pillar/src/styles/components/timeline"
|
||||
|
||||
@import "../../../pillar/src/styles/comments"
|
||||
@import "../../../pillar/src/styles/notifications"
|
||||
@@ -81,10 +86,6 @@
|
||||
@import "../../../pillar/src/styles/_project-dashboard"
|
||||
@import "../../../pillar/src/styles/_user"
|
||||
|
||||
@import _welcome
|
||||
@import _homepage
|
||||
@import _services
|
||||
@import _about
|
||||
@import "../../../pillar/src/styles/_search"
|
||||
@import "../../../pillar/src/styles/_organizations"
|
||||
|
||||
@@ -96,3 +97,12 @@
|
||||
@import "../../../pillar/src/styles/plugins/_js_select2"
|
||||
|
||||
/* CSS for pillar-font comes from fontello.com using static/assets/font/config.json */
|
||||
|
||||
@import variables
|
||||
@import utils
|
||||
|
||||
@import welcome
|
||||
@import services
|
||||
@import about
|
||||
@import homepage
|
||||
@import list_films
|
||||
|
@@ -7,6 +7,9 @@
|
||||
@import "../../../pillar/src/styles/_config"
|
||||
@import "../../../pillar/src/styles/_utils"
|
||||
|
||||
$pillar-font-path: "../../../../static/pillar/assets/font"
|
||||
@import "../../../pillar/src/styles/font-pillar"
|
||||
|
||||
// Bootstrap components.
|
||||
@import "../../../pillar/node_modules/bootstrap/scss/root"
|
||||
@import "../../../pillar/node_modules/bootstrap/scss/reboot"
|
||||
@@ -58,6 +61,8 @@
|
||||
@import "../../../pillar/src/styles/components/shortcode"
|
||||
@import "../../../pillar/src/styles/components/statusbar"
|
||||
@import "../../../pillar/src/styles/components/search"
|
||||
@import "../../../pillar/src/styles/components/placeholder"
|
||||
@import "../../../pillar/src/styles/components/timeline"
|
||||
|
||||
@import "../../../pillar/src/styles/components/flyout"
|
||||
@import "../../../pillar/src/styles/components/forms"
|
||||
@@ -68,6 +73,7 @@
|
||||
@import "../../../pillar/src/styles/components/checkbox"
|
||||
@import "../../../pillar/src/styles/components/overlay"
|
||||
@import "../../../pillar/src/styles/components/card"
|
||||
@import "../../../pillar/src/styles/components/breadcrumbs"
|
||||
|
||||
@import "../../../pillar/src/styles/_notifications"
|
||||
@import "../../../pillar/src/styles/_comments"
|
||||
@@ -83,4 +89,5 @@
|
||||
@import "../../../pillar/src/styles/plugins/_js_select2"
|
||||
|
||||
// Cloud components.
|
||||
@import variables
|
||||
@import "_project-landing"
|
||||
|
@@ -13,8 +13,8 @@
|
||||
powered by Free and Open Source Software.
|
||||
|
||||
h5.d-flex
|
||||
a.px-2(href="https://twitter.com/Blender_Cloud",
|
||||
title="Follow us on Twitter")
|
||||
a.px-2(href="https://www.youtube.com/BlenderCloudOfficial",
|
||||
title="Blender Cloud YouTube Channel")
|
||||
i.pi-social-youtube
|
||||
|
||||
a.px-2(href="https://twitter.com/Blender_Cloud",
|
||||
@@ -27,9 +27,10 @@
|
||||
|
||||
.col-md-2.col-xs-6
|
||||
h7.font-weight-bold
|
||||
| TRAINING
|
||||
a.d-block.pb-2(href="{{ url_for('cloud.learn') }}")
|
||||
| TRAINING
|
||||
|
||||
ul.list-unstyled
|
||||
ul.list-unstyled.mb-3
|
||||
li
|
||||
a(href="{{ url_for('cloud.courses') }}")
|
||||
| Courses
|
||||
@@ -37,12 +38,17 @@
|
||||
a(href="{{ url_for('cloud.workshops') }}")
|
||||
| Workshops
|
||||
li
|
||||
a(href="{{ url_for('projects.view', project_url='gallery') }}")
|
||||
| Art Gallery
|
||||
a(href="{{ url_for('cloud.production') }}")
|
||||
span.new Production Lessons
|
||||
|
||||
h7.font-weight-bold
|
||||
a.d-block.pb-2(href="{{ url_for('cloud.open_projects') }}")
|
||||
| FILMS
|
||||
|
||||
.col-md-2.col-xs-6
|
||||
h7.font-weight-bold
|
||||
| LIBRARIES
|
||||
a.d-block.pb-2(href="{{ url_for('cloud.libraries') }}")
|
||||
| LIBRARIES
|
||||
|
||||
ul.list-unstyled
|
||||
li
|
||||
@@ -57,10 +63,13 @@
|
||||
a(href="{{ url_for('projects.view', project_url='characters') }}",
|
||||
title="Characters")
|
||||
| Characters
|
||||
li
|
||||
a(href="{{ url_for('projects.view', project_url='gallery') }}")
|
||||
| Art Gallery
|
||||
|
||||
.col-md-2.col-xs-6
|
||||
h7.font-weight-bold
|
||||
a(href="{{ url_for('cloud.services') }}")
|
||||
a.d-block.pb-2(href="{{ url_for('cloud.services') }}")
|
||||
| SERVICES
|
||||
|
||||
ul.list-unstyled
|
||||
@@ -87,7 +96,9 @@
|
||||
|
||||
.col-md-2.col-xs-6
|
||||
h7.font-weight-bold
|
||||
| RESOURCES
|
||||
a.d-block.pb-2(href="{{ url_for('main.homepage') }}")
|
||||
| CLOUD
|
||||
|
||||
ul.list-unstyled
|
||||
li
|
||||
a(href="{{ url_for('main.main_blog') }}",
|
||||
@@ -100,7 +111,8 @@
|
||||
li
|
||||
a(href="{{ url_for('cloud.privacy') }}",
|
||||
title="Privacy")
|
||||
| Privacy
|
||||
| Privacy Policy
|
||||
li.dropdown-divider
|
||||
li
|
||||
a(href="https://www.blender.org",
|
||||
title="Home of Blender, the Free and Open Source creative suite")
|
||||
|
@@ -1,55 +1,174 @@
|
||||
include ../../../../pillar/src/templates/mixins/components
|
||||
|
||||
| {#
|
||||
| Secondary Navigation Bars.
|
||||
| #}
|
||||
|
||||
| {% macro navigation_homepage(title) %}
|
||||
+nav-secondary()
|
||||
button.navbar-toggler(
|
||||
type="button",
|
||||
data-toggle="collapse",
|
||||
data-target="#navigationLinks",
|
||||
aria-controls="navigationLinks",
|
||||
aria-expanded="false",
|
||||
aria-label="Toggle navigation"
|
||||
)
|
||||
i.pi-blender-cloud
|
||||
i.pi-angle-down
|
||||
+nav-secondary(class="collapse navbar-collapse")#navigationLinks
|
||||
+nav-secondary-link(
|
||||
href="{{ url_for('cloud.open_projects') }}")
|
||||
href="{{ url_for('main.homepage') }}")
|
||||
i.pi-blender-cloud-logo
|
||||
|
||||
+nav-secondary-link(
|
||||
href="{{ url_for('cloud.open_projects') }}",
|
||||
class="{% if title == 'films' %}active{% endif %}")
|
||||
span Films
|
||||
|
||||
+nav-secondary-link(
|
||||
href="{{ url_for('cloud.courses') }}")
|
||||
span Courses
|
||||
href="{{ url_for('cloud.learn') }}",
|
||||
class="{% if title in ('learn', 'courses', 'workshops') %}active{% endif %}")
|
||||
span Training
|
||||
|
||||
+nav-secondary-link(
|
||||
href="{{ url_for('cloud.workshops') }}")
|
||||
span Workshops
|
||||
href="{{ url_for('cloud.libraries') }}",
|
||||
class="{% if title == 'libraries' %}active{% endif %}")
|
||||
span Libraries
|
||||
|
||||
+nav-secondary-link(
|
||||
href="{{ url_for('projects.view', project_url='textures') }}")
|
||||
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') }}")
|
||||
href="{{ url_for('cloud.services') }}",
|
||||
class="{% if title == 'services' %}active{% endif %}")
|
||||
span Services
|
||||
| {% endmacro %}
|
||||
|
||||
| {% macro navigation_collection(title) %}
|
||||
+nav-secondary
|
||||
| {% if title in ['courses', 'workshops', 'production'] %}
|
||||
|
||||
| {% macro navigation_home_project(title) %}
|
||||
button.navbar-toggler(
|
||||
type="button",
|
||||
data-toggle="collapse",
|
||||
data-target="#navigationLinks",
|
||||
aria-controls="navigationLinks",
|
||||
aria-expanded="false",
|
||||
aria-label="Toggle navigation"
|
||||
)
|
||||
i.pi-blender-cloud
|
||||
i.pi-angle-down
|
||||
+nav-secondary(class="collapse navbar-collapse")#navigationLinks
|
||||
+nav-secondary-link(
|
||||
class="{% if title == 'courses' %}active{% endif %}",
|
||||
href="{{ url_for('cloud.courses') }}")
|
||||
span Courses
|
||||
href="{{ url_for('main.homepage') }}")
|
||||
i.pi-blender-cloud
|
||||
|
||||
+nav-secondary-link(
|
||||
class="{% if title == 'workshops' %}active{% endif %}",
|
||||
href="{{ url_for('cloud.workshops') }}")
|
||||
span Workshops
|
||||
href="{{ url_for('projects.index') }}",
|
||||
class="{% if title == 'dashboard' %}active{% endif %}")
|
||||
i.pi-star.pr-2
|
||||
span My Projects
|
||||
|
||||
| {% if current_user.has_organizations() %}
|
||||
+nav-secondary-link(
|
||||
href="{{ url_for('pillar.web.organizations.index') }}",
|
||||
class="{% if title == 'organizations' %}active{% endif %}")
|
||||
i.pi-users.pr-2
|
||||
span My Organizations
|
||||
| {% endif %}
|
||||
|
||||
+nav-secondary-link(
|
||||
class="{% if title == 'production' %}active{% endif %}",
|
||||
href="{{ url_for('cloud.production') }}")
|
||||
span.new Production Lessons
|
||||
href="{{ url_for('projects.home_project_shared_images')}}",
|
||||
class="{% if title == 'images' %}active{% endif %}")
|
||||
i.pi-picture.pr-2
|
||||
span Image Sharing
|
||||
|
||||
| {% elif title in ['open-projects'] %}
|
||||
+nav-secondary-link(
|
||||
class="{% if title == 'open-projects' %}active{% endif %}",
|
||||
href="{{ url_for('projects.view', project_url='gallery') }}")
|
||||
span Open Projects
|
||||
href="{{ url_for('projects.home_project') }}",
|
||||
class="{% if title == 'blender-sync' %}active{% endif %}")
|
||||
i.pi-blender.pr-2
|
||||
span Blender Sync
|
||||
| {% endmacro %}
|
||||
|
||||
|
||||
| {% macro navigation_project(project, navigation_links, extension_sidebar_links, title) %}
|
||||
|
||||
| {% if project.category == 'course' %}
|
||||
| {% set category_url = url_for('cloud.courses') %}
|
||||
| {% set category_title = 'Courses' %}
|
||||
|
||||
| {% elif project.category == 'workshop' %}
|
||||
| {% set category_url = url_for('cloud.workshops') %}
|
||||
| {% set category_title = 'Workshops' %}
|
||||
|
||||
| {% elif project.category == 'film' %}
|
||||
| {% set category_url = url_for('cloud.open_projects') %}
|
||||
| {% set category_title = 'Films' %}
|
||||
|
||||
| {% elif project.category == 'assets' %}
|
||||
| {% set category_url = url_for('cloud.libraries') %}
|
||||
| {% set category_title = 'Libraries' %}
|
||||
|
||||
| {% else %}
|
||||
| {% set category_url = url_for('main.homepage') %}
|
||||
| {% set category_title = project.category %}
|
||||
| {% endif %}
|
||||
|
||||
button.navbar-toggler(
|
||||
type="button",
|
||||
data-toggle="collapse",
|
||||
data-target="#navigationLinks",
|
||||
aria-controls="navigationLinks",
|
||||
aria-expanded="false",
|
||||
aria-label="Toggle navigation"
|
||||
)
|
||||
i.pi-blender-cloud
|
||||
i.pi-angle-down
|
||||
|
||||
+nav-secondary(class="collapse navbar-collapse")#navigationLinks
|
||||
|
||||
//- Blender Cloud logo.
|
||||
+nav-secondary-link(
|
||||
href="{{ url_for('main.homepage') }}")
|
||||
i.pi-blender-cloud
|
||||
|
||||
//- Category (Films, Courses, etc).
|
||||
+nav-secondary-link(
|
||||
href="{{ category_url }}",
|
||||
class="px-0")
|
||||
span {{ category_title }}
|
||||
li(class="nav-item px-1")
|
||||
i.pi-angle-right
|
||||
|
||||
//- Project Name.
|
||||
| {% if project.url != 'blender-cloud' %}
|
||||
+nav-secondary-link(
|
||||
class="font-weight-bold{% if title == 'default' %} active{% endif %} px-0",
|
||||
href="{{url_for('projects.view', project_url=project.url, _external=True)}}")
|
||||
span {{ project.name }}
|
||||
| {% endif %}
|
||||
|
||||
//- Pages (Blog, Team, Awards, etc).
|
||||
| {% for link in navigation_links %}
|
||||
+nav-secondary-link(
|
||||
href="{{ link['url'] }}",
|
||||
class="{% if link['slug'] == title %}active{% endif %}")
|
||||
span {{ link['label'] }}
|
||||
| {% endfor %}
|
||||
|
||||
+nav-secondary-link(
|
||||
href="{{ url_for('cloud.project_browse', project_url=project.url) }}",
|
||||
title="Browse {{ project.name }}",
|
||||
class="{% if title == 'project' %}active{% endif %}")
|
||||
span Browse
|
||||
|
||||
//- Link to Production Tools (Attract, Flamenco, SVN, etc).
|
||||
| {% if extension_sidebar_links %}
|
||||
+nav-secondary()
|
||||
li.nav-item.dropdown
|
||||
a.nav-link.dropdown-toggle(
|
||||
class="{% if title == 'production-tools' %}active{% endif %}"
|
||||
href="#"
|
||||
data-toggle="dropdown")
|
||||
span Production Tools
|
||||
i.pi-angle-down
|
||||
|
||||
ul.dropdown-menu
|
||||
| {{ extension_sidebar_links }}
|
||||
| {% endif %}
|
||||
| {% endmacro %}
|
||||
|
17
src/templates/_macros/_opengraph.pug
Normal file
@@ -0,0 +1,17 @@
|
||||
//- 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 %}
|
7
src/templates/design_system.pug
Normal file
@@ -0,0 +1,7 @@
|
||||
| {% 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
|
||||
CG pipeline in the world. You rock!
|
||||
p.buttons
|
||||
a.button(href="{{ abs_url('cloud.login', next='/') }}", target='_blank') Explore Now >
|
||||
a.button(href="{{ abs_url('cloud.login', next='/') }}", target='_blank') Browse Now >
|
||||
|
||||
p.
|
||||
Here is a quick guide to help you get started with Blender Cloud.
|
||||
|
61
src/templates/films.pug
Normal file
@@ -0,0 +1,61 @@
|
||||
| {% 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,69 +25,78 @@ meta(name="twitter:image", content="{% if main_project.picture_header %}{{ main_
|
||||
| {{ navigation_homepage(title) }}
|
||||
| {% endblock navigation_tabs %}
|
||||
|
||||
mixin featured_project_card(title, description, url, image)
|
||||
a.featured-project-card.card.asset.my-2(href=url)
|
||||
img.card-thumbnail(alt=title, src=image)
|
||||
.card-body.py-2()
|
||||
.card-title.mb-1.font-weight-bold
|
||||
=title
|
||||
.card-text
|
||||
=description
|
||||
|
||||
.btn-link
|
||||
| See more
|
||||
i.pi-angle-right
|
||||
|
||||
mixin featured_projects()
|
||||
section.py-2.mb-3
|
||||
h6.title-underline
|
||||
| Featured Projects
|
||||
|
||||
.featured-projects
|
||||
+card-deck(3)&attributes(attributes)
|
||||
+featured_project_card(
|
||||
"MASTER SHADING NODES",
|
||||
"Dive into a sea of nodes with this training by Simon Thommes.",
|
||||
"/p/procedural-shading",
|
||||
"{{ url_for('static', filename='assets/img/features/training_procedural_shading_01.jpg')}}")
|
||||
|
||||
+featured_project_card(
|
||||
"IMPROVE YOUR RIGS",
|
||||
"The ultimate guide to learn about this crucial step while rigging.",
|
||||
"/p/weight-painting",
|
||||
"{{ url_for('static', filename='assets/img/features/training_weight_painting_01.jpg')}}")
|
||||
|
||||
+featured_project_card(
|
||||
"COFFEE RUN",
|
||||
"This 2d-scroller-inspired short film will take you on the journey of a lifetime.",
|
||||
"/p/coffee-run",
|
||||
"{{ url_for('static', filename='assets/img/features/coffee_run_02.jpg')}}")
|
||||
|
||||
|
||||
|
||||
| {% block body %}
|
||||
.container-fluid.dashboard-container.imgs-fluid
|
||||
.row
|
||||
.col-md-8.col-xl-9
|
||||
section.blog
|
||||
| {% if latest_posts %}
|
||||
| {% for node in latest_posts %}
|
||||
| {{ render_blog_post(node) }}
|
||||
| {% endfor %}
|
||||
| {% else %}
|
||||
| No blog entries... yet!
|
||||
| {% endif %}
|
||||
.row.mt-3
|
||||
.col-md-10.col-lg-9.col-xl-8.mx-auto
|
||||
.d-xl-none
|
||||
+featured_projects()
|
||||
|
||||
.d-block.text-center
|
||||
a.d-inline-block.p-3.text-muted(href="{{ url_for('main.main_blog') }}")
|
||||
| See All Blog Posts
|
||||
+timeline()
|
||||
|
||||
a.d-inline-block.p-3.text-muted(
|
||||
href="{{ url_for('main.feeds_blogs') }}",
|
||||
title="Blogs Feed",
|
||||
data-toggle="tooltip",
|
||||
data-placement="left")
|
||||
i.pi-rss
|
||||
| RSS Feed
|
||||
.d-block.text-center
|
||||
a.d-inline-block.p-3.text-muted(href="{{ url_for('main.main_blog') }}")
|
||||
| See All Blog Posts
|
||||
|
||||
.col-md-4.col-xl-3
|
||||
section.pt-3
|
||||
h6.title-underline
|
||||
a.text-muted(href="{{ url_for('cloud.open_projects') }}")
|
||||
| Films In Production
|
||||
a.d-inline-block.p-3.text-muted(
|
||||
href="{{ url_for('main.feeds_blogs') }}",
|
||||
title="Blogs Feed",
|
||||
data-toggle="tooltip",
|
||||
data-placement="left")
|
||||
i.pi-rss
|
||||
| RSS Feed
|
||||
|
||||
a(href="/p/spring/")
|
||||
img.rounded(
|
||||
alt="Spring Open Movie Project",
|
||||
src="{{ url_for('static', filename='assets/img/projects/spring_02_450x150.jpg')}}")
|
||||
.col-md-10.col-lg-9.col-xl-4.mx-auto
|
||||
.d-lg-none.d-xl-block
|
||||
+featured_projects()(class="card-deck-vertical border-bottom pb-3")
|
||||
|
||||
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
|
||||
section.py-2.border-bottom.mb-3
|
||||
h6.title-underline
|
||||
a.text-muted(href="{{ url_for('main.nodes_search_index') }}")
|
||||
| Random Awesome
|
||||
|
||||
| {% if random_featured %}
|
||||
+card-deck()(class='card-deck-vertical pl-3')
|
||||
+card-deck()(class='pl-3 random-featured')
|
||||
| {% for child in random_featured %}
|
||||
| {% if child.node_type not in ['comment'] %}
|
||||
| {{ asset_list_item(child, current_user) }}
|
||||
@@ -131,6 +140,7 @@ meta(name="twitter:image", content="{% if main_project.picture_header %}{{ main_
|
||||
|
||||
| {% endif %}
|
||||
|
||||
|
||||
| {% endblock %}
|
||||
|
||||
|
||||
|
@@ -32,6 +32,9 @@ html(lang="en")
|
||||
| {% endblock og %}
|
||||
|
||||
script(src="{{ url_for('static_pillar', filename='assets/js/tutti.min.js') }}")
|
||||
script.
|
||||
pillar.utils.initCurrentUser({{ current_user | json | safe }});
|
||||
script(src="{{ url_for('static_pillar', filename='assets/js/timeline.min.js') }}")
|
||||
script(src="{{ url_for('static_pillar', filename='assets/js/vendor/jquery.typeahead-0.11.1.min.js')}}")
|
||||
script(src="{{ url_for('static_pillar', filename='assets/js/vendor/js.cookie-2.0.3.min.js')}}")
|
||||
| {% if current_user.is_authenticated %}
|
||||
@@ -44,7 +47,6 @@ html(lang="en")
|
||||
| {% block head %}{% endblock %}
|
||||
|
||||
| {% block css %}
|
||||
link(href="{{ url_for('static_pillar', filename='assets/css/font-pillar.css') }}", rel="stylesheet")
|
||||
| {% if title == 'blog' %}
|
||||
link(href="{{ url_for('static_pillar', filename='assets/css/blog.css') }}", rel="stylesheet")
|
||||
| {% else %}
|
||||
@@ -53,11 +55,23 @@ html(lang="en")
|
||||
| {% endblock css %}
|
||||
|
||||
| {% if not title %}{% set title="default" %}{% endif %}
|
||||
body(class="{{ title }}")
|
||||
body(class="{{ title }} {{'project' if project and project.url != 'blender-cloud'}} {% block bodyclasses %}{% endblock %}"
|
||||
"{% block bodyattrs %}{% endblock %}"
|
||||
)
|
||||
| {% with messages = get_flashed_messages(with_categories=True) %}
|
||||
| {% if messages %}
|
||||
| {% if messages or (config.UI_ANNOUNCEMENT_NON_SUBSCRIBERS and not current_user.has_cap('subscriber')) %}
|
||||
| {% if config.UI_ANNOUNCEMENT_NON_SUBSCRIBERS %}
|
||||
.alert.d-flex.justify-content-center(
|
||||
role="alert",
|
||||
class="alert-{{ config.UI_ANNOUNCEMENT_NON_SUBSCRIBERS['category'] }}")
|
||||
i.pr-2(class="{{ config.UI_ANNOUNCEMENT_NON_SUBSCRIBERS['icon'] }}")
|
||||
| {{ config.UI_ANNOUNCEMENT_NON_SUBSCRIBERS['message'] | markdown }}
|
||||
| {% endif %}
|
||||
|
||||
| {% for (category, message) in messages %}
|
||||
.alert(role="alert", class="alert-{{ category }}")
|
||||
.alert.d-flex.justify-content-center(
|
||||
role="alert",
|
||||
class="alert-{{ category }}")
|
||||
i.alert-icon(class="{{ category }}")
|
||||
span {{ message }}
|
||||
button.close(type="button", data-dismiss="alert")
|
||||
@@ -67,29 +81,14 @@ html(lang="en")
|
||||
| {% endwith %}
|
||||
|
||||
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 %}
|
||||
+nav-secondary(class="collapse navbar-collapse")#navigationLinks
|
||||
+nav-secondary-link(
|
||||
href="{{ url_for('main.homepage') }}")
|
||||
i.pi-blender-cloud-logo
|
||||
| {% endblock navigation_tabs %}
|
||||
|
||||
| {% block navigation_search %}
|
||||
| {% endblock navigation_search %}
|
||||
+nav-secondary()(class="m-auto keep-when-overlay")
|
||||
div.nav-item.quick-search.qs-input#qs-input
|
||||
|
||||
+nav-secondary()(class="ml-auto")
|
||||
| {% if node and node.properties and node.properties.category %}
|
||||
@@ -97,17 +96,9 @@ html(lang="en")
|
||||
| {% else %}
|
||||
| {% set category = title %}
|
||||
| {% endif %}
|
||||
li.nav-item.quick-search.cursor-pointer.px-3.pi-search#qs-toggle
|
||||
|
||||
| {% 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 %}
|
||||
|
||||
| {% block navigation_user %}
|
||||
@@ -122,10 +113,13 @@ html(lang="en")
|
||||
title="Sign up") Sign up
|
||||
| {% endif %}
|
||||
|
||||
.loader-bar
|
||||
.loading-bar
|
||||
|
||||
.page-content
|
||||
#search-overlay
|
||||
.quick-search.container-fluid.m-auto.p-5#search-overlay
|
||||
ul.qs-loading.text-center
|
||||
i.h1.pi-spin.spinner
|
||||
h2 Loading
|
||||
| {% block page_overlay %}
|
||||
#page-overlay
|
||||
| {% endblock page_overlay %}
|
||||
@@ -145,7 +139,6 @@ html(lang="en")
|
||||
.nc-text
|
||||
span.nc-date
|
||||
a(href="")
|
||||
|
||||
| {% if current_user.is_authenticated %}
|
||||
script(src="{{ url_for('static_pillar', filename='assets/js/vendor/jquery.typewatch-3.0.0.min.js') }}")
|
||||
script.
|
||||
@@ -175,15 +168,36 @@ html(lang="en")
|
||||
$('[data-toggle="tooltip"]').tooltip({'delay' : {'show': 0, 'hide': 0}});
|
||||
}
|
||||
|
||||
// Main dropdown menu logic.
|
||||
$('[data-toggle="dropdown-tab"]').hover(function(){
|
||||
let tab = $(this).data('tab-target');
|
||||
// Enable Quick Search
|
||||
let searches = {
|
||||
{% if project and not project.is_private %}
|
||||
project: {
|
||||
name: 'Project',
|
||||
uiUrl: '{{ url_for("projects.search", project_url=project.url)}}',
|
||||
apiUrl: '/api/newsearch/multisearch',
|
||||
searchParams: [
|
||||
{name: 'Assets', params: {project: '{{ project._id }}', node_type: 'asset'}},
|
||||
{name: 'Blog', params: {project: '{{ project._id }}', node_type: 'post'}},
|
||||
{name: 'Groups', params: {project: '{{ project._id }}', node_type: 'group'}},
|
||||
]
|
||||
},
|
||||
{% endif %}
|
||||
cloud: {
|
||||
name: 'Cloud',
|
||||
uiUrl: '/search',
|
||||
apiUrl: '/api/newsearch/multisearch',
|
||||
searchParams: [
|
||||
{name: 'Assets', params: {node_type: 'asset'}},
|
||||
{name: 'Blog', params: {node_type: 'post'}},
|
||||
{name: 'Groups', params: {node_type: 'group'}},
|
||||
]
|
||||
},
|
||||
}
|
||||
|
||||
$('[data-toggle="dropdown-tab"]').removeClass('active');
|
||||
$(this).addClass('active');
|
||||
|
||||
$('[data-tab]').removeClass('show');
|
||||
$('[data-tab="' + tab + '"]').addClass('show');
|
||||
$('#qs-toggle').quickSearch({
|
||||
resultTarget: '#search-overlay',
|
||||
inputTarget: '#qs-input',
|
||||
searches: searches,
|
||||
});
|
||||
|
||||
| {% block footer_scripts_pre %}{% endblock %}
|
||||
|
103
src/templates/learn.pug
Normal file
@@ -0,0 +1,103 @@
|
||||
| {% 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 %}
|
128
src/templates/libraries.pug
Normal file
@@ -0,0 +1,128 @@
|
||||
| {% 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 %}
|
@@ -1,177 +0,0 @@
|
||||
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,10 +10,7 @@
|
||||
|
||||
| {% block menu_avatar %}
|
||||
a.navbar-item.dropdown-toggle(href="{{ url_for('settings.profile') }}", data-toggle="dropdown")
|
||||
img.gravatar.rounded-circle(
|
||||
src="{{ current_user.gravatar }}",
|
||||
class="{{ subscription }}",
|
||||
alt="Avatar")
|
||||
current-user-avatar
|
||||
.special(class="{{ subscription }}")
|
||||
| {% if subscription == 'subscriber' %}
|
||||
i.pi-check
|
||||
@@ -22,6 +19,8 @@ a.navbar-item.dropdown-toggle(href="{{ url_for('settings.profile') }}", data-tog
|
||||
| {% else %}
|
||||
i.pi-attention
|
||||
| {% endif %}
|
||||
script.
|
||||
new Vue({el: 'current-user-avatar'})
|
||||
| {% endblock menu_avatar %}
|
||||
|
||||
|
||||
|
33
src/templates/mixins/components.pug
Normal file
@@ -0,0 +1,33 @@
|
||||
//- 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,13 +1,12 @@
|
||||
include ../../../../../../pillar/src/templates/mixins/components
|
||||
|
||||
| {% import 'projects/_macros.html' as projectmacros %}
|
||||
| {% macro render_blog_post(node, project=None, pages=None) %}
|
||||
|
||||
.expand-image-links.imgs-fluid
|
||||
| {% if node.picture %}
|
||||
+jumbotron(
|
||||
"{{ node.name }}",
|
||||
"{{ node._created | pretty_date }}{% if node.user.full_name %} · {{ node.user.full_name }}{% endif %}",
|
||||
"{{ node._created | pretty_date }}{% if node.user.full_name %} · {{ node.user.full_name }}{% endif %}{% if node.properties.status != 'published' %} · {{ node.properties.status }}{% endif %}",
|
||||
"{{ node.picture.thumbnail('h', api=api) }}",
|
||||
"{{ node.url }}")(
|
||||
class="jumbotron-overlay")
|
||||
@@ -17,6 +16,10 @@ include ../../../../../../pillar/src/templates/mixins/components
|
||||
a.text-muted(href="{{ node.url }}")
|
||||
| {{ node.name }}
|
||||
ul.d-flex.list-unstyled.justify-content-center
|
||||
| {% if node.properties.status != 'published' %}
|
||||
li.mr-3(title="Status {{ node.properties.status }}")
|
||||
span.badge.badge-danger {{ node.properties.status | undertitle }}
|
||||
| {% endif %}
|
||||
| {% if node.project.name %}
|
||||
li.pr-2 {{ node.project.name }}
|
||||
| {% endif %}
|
||||
@@ -38,18 +41,21 @@ include ../../../../../../pillar/src/templates/mixins/components
|
||||
|
||||
hr.my-4
|
||||
|
||||
#comments-embed.d-flex.justify-content-center.mx-auto
|
||||
comments-tree#comments-embed.justify-content-center.mx-auto(
|
||||
parent-id="{{ node._id }}"
|
||||
read-only=false
|
||||
)
|
||||
| {% endmacro %}
|
||||
|
||||
//- ******************************************************* -//
|
||||
| {% macro render_blog_list_item(node) %}
|
||||
a.card.asset.card-image-fade.pr-0.mx-0.mb-4(
|
||||
a.card.asset.card-image-fade(
|
||||
href="{{ node.url }}")
|
||||
.embed-responsive.embed-responsive-16by9
|
||||
.card-thumbnail
|
||||
| {% if node.picture %}
|
||||
.card-img-top.embed-responsive-item(style="background-image: url({{ node.picture.thumbnail('m', api=api) }})")
|
||||
img.card-img-top(src="{{ node.picture.thumbnail('m', api=api) }}", alt="{{ node.name }}")
|
||||
| {% else %}
|
||||
.card-img-top.card-icon.embed-responsive-item
|
||||
.card-img-top
|
||||
i.pi-document-text
|
||||
| {% endif %}
|
||||
|
||||
@@ -70,14 +76,14 @@ a.card.asset.card-image-fade.pr-0.mx-0.mb-4(
|
||||
//- ******************************************************* -//
|
||||
| {% 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.has_method('PUT') %}
|
||||
+nav-secondary
|
||||
| {% if can_create_blog_posts or (current_post and current_post.has_method('PUT')) %}
|
||||
+nav-secondary(class="bg-light border-bottom")
|
||||
| {% if can_create_blog_posts %}
|
||||
+nav-secondary-link(href="{{url_for('nodes.posts_create', project_id=project._id)}}")
|
||||
i.pi-plus.pr-2
|
||||
span Create New Blog Post
|
||||
| {% endif %}
|
||||
| {% if current_post.has_method('PUT') %}
|
||||
| {% if (current_post and current_post.has_method('PUT')) %}
|
||||
+nav-secondary-link(href="{{url_for('nodes.edit', node_id=current_post._id)}}")
|
||||
i.pi-edit.pr-2
|
||||
span Edit Post
|
||||
@@ -97,7 +103,7 @@ a.card.asset.card-image-fade.pr-0.mx-0.mb-4(
|
||||
| More from {{ project.name }} blog
|
||||
| {% endif %}
|
||||
|
||||
+card-deck(class="px-2 justify-content-center")
|
||||
+card-deck(class="px-2")
|
||||
| {% for node in posts %}
|
||||
| {# Skip listing the current post #}
|
||||
| {% if node._id != current_post._id %}
|
||||
|
@@ -1,5 +1,5 @@
|
||||
| {% extends 'layout.html' %}
|
||||
| {% from '_macros/_navigation.html' import navigation_tabs %}
|
||||
| {% from '_macros/_navigation.html' import navigation_home_project %}
|
||||
include ../../../../pillar/src/templates/mixins/components
|
||||
|
||||
| {% set title = 'organizations' %}
|
||||
@@ -16,9 +16,8 @@ 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')}}")
|
||||
| {% endblock %}
|
||||
|
||||
|
||||
| {% block navigation_tabs %}
|
||||
| {{ navigation_tabs(title) }}
|
||||
| {{ navigation_home_project(title) }}
|
||||
| {% endblock navigation_tabs %}
|
||||
|
||||
| {% block body %}
|
||||
@@ -27,8 +26,8 @@ meta(name="twitter:image", content="{{ url_for('static', filename='assets/img/ba
|
||||
+nav-secondary-link(
|
||||
class="create",
|
||||
onclick='createNewOrganization(this)')
|
||||
i.pi-plus.text-success
|
||||
span.text-success
|
||||
i.pi-plus
|
||||
| Create Organization
|
||||
| {% endif %}
|
||||
|
||||
@@ -54,7 +53,7 @@ meta(name="twitter:image", content="{{ url_for('static', filename='assets/img/ba
|
||||
ul.meta
|
||||
li(title="Members")
|
||||
| {{ organization.members|hide_none|count }} Member{{ organization.members|hide_none|count|pluralize }}
|
||||
| {% if (organization.unknown_members|count) != 0 %}
|
||||
| {% if (organization.unknown_members|hide_none|count) != 0 %}
|
||||
| ({{ organization.unknown_members|hide_none|count }} pending)
|
||||
| {% endif %}
|
||||
li(title="Seats")
|
||||
|
@@ -15,7 +15,7 @@ style.
|
||||
This Application collects some Personal Data from its Users.
|
||||
h3 Data Controller and Owner
|
||||
p.
|
||||
Blender Institute B.V. - Entrepotdok 57A - 1018 AD Amsterdam - the Netherlands,
|
||||
Blender Institute B.V. - Buikslotermeerplein 161 - 1025 ET Amsterdam - the Netherlands,
|
||||
institute@blender.org
|
||||
p.
|
||||
Blender Institute has been authorised by Stichting Blender Foundation to conduct these
|
||||
@@ -121,7 +121,8 @@ style.
|
||||
Data Controller to erase the Personal Data. Unless stated otherwise, the then-current privacy
|
||||
policy applies to all Personal Data the Data Controller has about Users.
|
||||
h4 Definitions and legal references
|
||||
p Latest update: February 27, 2014
|
||||
p Original issue: February 27, 2014
|
||||
p Latest update: June 10, 2019 (Updated Blender Institute address)
|
||||
|
||||
|
||||
| {% endblock body%}
|
||||
|
@@ -1,20 +1,31 @@
|
||||
| {% extends 'layout.html' %}
|
||||
| {% from '_macros/_navigation.html' import navigation_collection %}
|
||||
|
||||
| {% from '_macros/_navigation.html' import navigation_homepage %}
|
||||
| {% from '_macros/_opengraph.html' import opengraph %}
|
||||
include mixins/components
|
||||
include ../../../pillar/src/templates/mixins/components
|
||||
|
||||
mixin group(title, tag)
|
||||
.row
|
||||
section.py-3.my-3.border-bottom.col-12
|
||||
.row(id=tag)
|
||||
section.py-4.my-3.border-bottom.col-12
|
||||
|
||||
h4.title-underline.mb-4= title
|
||||
+card-deck(data-asset-tag=tag, class="js-asset-list py-3")
|
||||
h4.title-underline.mt-2.mb-4
|
||||
a.text-muted(href="#" + tag)= title
|
||||
+card-deck(data-asset-tag=tag, class="js-asset-list p-3")
|
||||
|
||||
| {% block page_title %}Production Lessons{% endblock %}
|
||||
| {% set page_header_text = "Tips and tricks by the Open Movie crew." %}
|
||||
| {% set title = 'learn' %}
|
||||
|
||||
| {% set page_title = 'Production Lessons' %}
|
||||
| {% set page_description = 'Tips and tricks by the Blender Open Movies crew.' %}
|
||||
| {% set page_header_image = url_for('static', filename='assets/img/features/open_movies_02.jpg', _external=true) %}
|
||||
|
||||
| {% block page_title %}{{ page_title }}{% endblock %}
|
||||
|
||||
| {% block og %}
|
||||
| {{ opengraph(page_title, page_description, page_header_image, request.url) }}
|
||||
| {% endblock %}
|
||||
|
||||
| {% block navigation_tabs %}
|
||||
| {{ navigation_collection(title) }}
|
||||
| {{ navigation_homepage(title) }}
|
||||
| {% endblock navigation_tabs %}
|
||||
|
||||
| {% block head %}
|
||||
@@ -22,31 +33,32 @@ script(src="{{ url_for('static_cloud', filename='assets/js/tagged_assets.min.js'
|
||||
|
||||
script.
|
||||
$(function() {
|
||||
$('.js-asset-list').loadTaggedAssets(5, 3);
|
||||
let is_subscriber = {{ current_user.has_cap('subscriber')|string|lower }};
|
||||
$('.js-asset-list').loadTaggedAssets(8, 8, is_subscriber);
|
||||
});
|
||||
| {% endblock %}
|
||||
| {% block body %}
|
||||
+jumbotron(
|
||||
'{{ self.page_title() }}',
|
||||
'{{ page_header_text }}',
|
||||
"{{ url_for('static', filename='assets/img/backgrounds/background_agent327_04.jpg')}}")(
|
||||
class="jumbotron-overlay")
|
||||
.container.py-4
|
||||
+category_list_header('{{ page_title }}', '{{ page_description }}')
|
||||
.row
|
||||
.col-12
|
||||
+group('Walk-through', 'walk-through')
|
||||
+group('Modeling', 'modeling')
|
||||
+group('Sculpting', 'sculpting')
|
||||
+group('Animation', 'animation')
|
||||
+group('Shading', 'shading')
|
||||
+group('Texturing', 'texturing')
|
||||
+group('Character Pipeline', 'character-pipeline')
|
||||
+group('Rigging', 'rigging')
|
||||
+group('Lighting & Rendering', 'lighting')
|
||||
+group('Simulation & Effects', 'effects')
|
||||
+group('Video Editing', 'video-editing')
|
||||
+group('Digital Painting', 'digital-painting')
|
||||
+group('Production Design', 'production-design')
|
||||
|
||||
.container
|
||||
+group('Modeling', 'modeling')
|
||||
+group('Sculpting', 'sculpting')
|
||||
+group('Animation', 'animation')
|
||||
+group('Shading', 'shading')
|
||||
+group('Texturing', 'texturing')
|
||||
+group('Character Pipeline', 'character-pipeline')
|
||||
+group('Rigging', 'rigging')
|
||||
+group('Lighting & Rendering', 'lighting')
|
||||
+group('Simulation & Effects', 'effects')
|
||||
+group('Video Editing', 'video-editing')
|
||||
|
||||
a.d-block.py-5.text-center.text-muted(
|
||||
href="{{ url_for('main.nodes_search_index') }}")
|
||||
| Search Blender Cloud to find even more content
|
||||
i.pi-angle-right.pl-1
|
||||
a.d-block.py-5.text-center.text-muted(
|
||||
href="{{ url_for('main.nodes_search_index') }}")
|
||||
| Search Blender Cloud to find even more content
|
||||
i.pi-angle-right.pl-1
|
||||
|
||||
| {% endblock body%}
|
||||
|
21
src/templates/project_settings/cloud_layout.pug
Normal file
@@ -0,0 +1,21 @@
|
||||
| {% 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 %}
|
28
src/templates/project_settings/offer_setup.pug
Normal file
@@ -0,0 +1,28 @@
|
||||
| {% 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 %}
|
66
src/templates/project_settings/settings.pug
Normal file
@@ -0,0 +1,66 @@
|
||||
| {% 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 %}
|
23
src/templates/projects/browse_embed.pug
Normal file
@@ -0,0 +1,23 @@
|
||||
| {% 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,4 +1,6 @@
|
||||
| {% extends 'projects/home_layout.html' %}
|
||||
| {% set title = 'blender-sync' %}
|
||||
|
||||
| {% set subtab = 'blender_sync' %}
|
||||
| {% set learn_more_btn_url = '/blog/introducing-blender-sync' %}
|
||||
| {% block currenttab %}
|
||||
|
@@ -1,9 +1,7 @@
|
||||
| {% extends 'layout.html' %}
|
||||
| {% from '_macros/_navigation.html' import navigation_tabs %}
|
||||
| {% from '_macros/_navigation.html' import navigation_home_project %}
|
||||
include ../../../../pillar/src/templates/mixins/components
|
||||
|
||||
| {% set title = 'home' %}
|
||||
|
||||
| {% block og %}
|
||||
meta(property="og:type", content="website")
|
||||
meta(property="og:url", content="https://cloud.blender.org{{ request.path }}")
|
||||
@@ -20,20 +18,13 @@ meta(name="twitter:image", content="{{ url_for('static', filename='assets/img/ba
|
||||
| {% endblock %}
|
||||
|
||||
| {% block navigation_tabs %}
|
||||
| {{ navigation_tabs(title) }}
|
||||
| {{ navigation_home_project(title) }}
|
||||
| {% endblock navigation_tabs %}
|
||||
|
||||
| {% block body %}
|
||||
.dashboard-container
|
||||
|
||||
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 %}
|
||||
| {% endblock %}
|
||||
|
||||
|
@@ -1,5 +1,5 @@
|
||||
| {% extends 'layout.html' %}
|
||||
| {% from '_macros/_navigation.html' import navigation_tabs %}
|
||||
| {% from '_macros/_navigation.html' import navigation_home_project %}
|
||||
include ../../../../pillar/src/templates/mixins/components
|
||||
|
||||
| {% set title = 'dashboard' %}
|
||||
@@ -39,10 +39,9 @@ style.
|
||||
| {% endblock %}
|
||||
|
||||
| {% block navigation_tabs %}
|
||||
| {{ navigation_tabs(title) }}
|
||||
| {{ navigation_home_project(title) }}
|
||||
| {% endblock navigation_tabs %}
|
||||
|
||||
|
||||
| {% block body %}
|
||||
.dashboard-container
|
||||
section.dashboard-main
|
||||
|
@@ -1,71 +1,25 @@
|
||||
| {% extends 'layout.html' %}
|
||||
| {% from '_macros/_navigation.html' import navigation_project %}
|
||||
| {% from '_macros/_opengraph.html' import opengraph %}
|
||||
|
||||
include ../../../../pillar/src/templates/mixins/components
|
||||
|
||||
| {% import 'projects/_macros.html' as projectmacros %}
|
||||
| {% block bodyclasses %}{{ super() }} landing-home{% endblock %}
|
||||
|
||||
| {% from '_macros/_asset_list_item.html' import asset_list_item %}
|
||||
|
||||
| {% block page_title %}{{ project.name }}{% endblock%}
|
||||
|
||||
| {% block og %}
|
||||
meta(property="og:type", content="website")
|
||||
|
||||
| {% if og_picture %}
|
||||
meta(property="og:image", content="{{ og_picture.thumbnail('l', api=api) }}")
|
||||
meta(name="twitter:image", content="{{ og_picture.thumbnail('l', api=api) }}")
|
||||
| {% elif node and node.picture %}
|
||||
meta(property="og:image", content="{{ node.picture.thumbnail('l', api=api) }}")
|
||||
meta(name="twitter:image", content="{{ node.picture.thumbnail('l', api=api) }}")
|
||||
| {% elif project.picture_header %}
|
||||
meta(property="og:image", content="{{ project.picture_header.thumbnail('l', api=api) }}")
|
||||
meta(name="twitter:image", content="{{ project.picture_header.thumbnail('l', api=api) }}")
|
||||
| {% endif %}
|
||||
|
||||
| {% if show_project %}
|
||||
meta(property="og:title", content="{{ project.name }} - Blender Cloud")
|
||||
meta(name="twitter:title", content="{{ project.name }} - Blender Cloud")
|
||||
meta(property="og:description", content="{{ project.summary }}")
|
||||
meta(name="twitter:description", content="{{ project.summary }}")
|
||||
meta(property="og:url", content="{{ url_for('projects.view', project_url=project.url, _external=True) }}")
|
||||
| {% set og_picture_url = og_picture.thumbnail('l', api=api) %}
|
||||
| {% 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")
|
||||
| {% set og_picture_url = None %}
|
||||
| {% endif %}
|
||||
|
||||
| {% else %}
|
||||
| {% block og %}
|
||||
| {{ opengraph(project.name, project.summary, og_picture_url, url_for('cloud.project_landing', project_url=project.url, _external=True)) }}
|
||||
| {% endblock %}
|
||||
|
||||
| {% 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 %}
|
||||
#page-overlay.video
|
||||
@@ -74,88 +28,155 @@ meta(property="og:url", content="{{url_for('projects.view', project_url=project.
|
||||
| {% endblock %}
|
||||
|
||||
| {% 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")
|
||||
| {% endblock %}
|
||||
|
||||
| {% block navigation_tabs %}
|
||||
| {{ projectmacros.render_secondary_navigation(project, navigation_links, title) }}
|
||||
| {{ navigation_project(project, navigation_links, extension_sidebar_links, title) }}
|
||||
| {% endblock navigation_tabs %}
|
||||
|
||||
| {% 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
|
||||
.row
|
||||
.col-md-8.mx-auto
|
||||
h2.pt-5 {{ project.name }}
|
||||
|
||||
| {% if project.description %}
|
||||
.col-md-8.mx-auto.mt-5
|
||||
.node-details-description
|
||||
| {% if project.description %}
|
||||
| {{ project | markdowned('description') }}
|
||||
| {% endif %}
|
||||
| {% endif %}
|
||||
|
||||
.row
|
||||
.col-md-10.mx-auto
|
||||
section.py-5
|
||||
h2.pb-3.text-center Gallery
|
||||
|
||||
.gallery
|
||||
.gallery.mx-auto
|
||||
| {% for n in activity_stream %}
|
||||
| {% if n.node_type not in ['comment', 'post'] and n.picture %}
|
||||
.thumbnail.expand-image-links
|
||||
.img-container
|
||||
a.js-open-overlay(href="{{ n.picture.thumbnail('l', api=api) }}", data-node_id="{{ n._id }}")
|
||||
img(src="{{ n.picture.thumbnail('l', api=api) }}", alt="{{ n.name }}")
|
||||
.img-caption.table
|
||||
| {# Not using for the moment
|
||||
span.table-cell {{ n.name }}
|
||||
| #}
|
||||
a.js-open-overlay-image(
|
||||
title="{{ n.name }}",
|
||||
href="{{ n.picture.thumbnail('l', api=api) }}")
|
||||
img(
|
||||
alt="{{ n.name }}",
|
||||
src="{{ n.picture.thumbnail('l', api=api) }}")
|
||||
| {% endif %}
|
||||
| {% endfor %}
|
||||
div(class="clearfix")
|
||||
| {% if project.nodes_featured %}
|
||||
| {# In some cases featured_nodes might might be embedded #}
|
||||
| {% if '_id' in project.nodes_featured[0] %}
|
||||
| {% set featured_node_id=project.nodes_featured[0]._id %}
|
||||
| {% else %}
|
||||
| {% set featured_node_id=project.nodes_featured[0] %}
|
||||
| {% endif %}
|
||||
.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
|
||||
| {% endif %}
|
||||
|
||||
.clearfix
|
||||
.text-center.mx-auto.py-3
|
||||
a.btn.btn-outline-primary.px-5(
|
||||
href="{{ project_browse_url }}")
|
||||
| See More Artwork
|
||||
|
||||
|
||||
.row
|
||||
.row.mt-5
|
||||
.col-md-10.mx-auto
|
||||
|
||||
h2.pb-3.text-center Latest Updates
|
||||
|
||||
| {% 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
|
||||
h2.pb-3 Project Timeline
|
||||
.timeline-dark
|
||||
+timeline("{{ project._id }}")
|
||||
|
||||
| {% endblock body %}
|
||||
|
||||
|
||||
| {% block footer_scripts %}
|
||||
script.
|
||||
// Click anywhere in the page to hide the overlay
|
||||
function hideOverlay() {
|
||||
$('#page-overlay.video').removeClass('active');
|
||||
$('#page-overlay.video .video-embed').html('');
|
||||
function showOverlay(html_content) {
|
||||
$('#page-overlay')
|
||||
.addClass('active')
|
||||
.html(html_content);
|
||||
}
|
||||
|
||||
function hideOverlay() {
|
||||
$('#page-overlay')
|
||||
.removeClass('active')
|
||||
.html('');
|
||||
}
|
||||
|
||||
$("a.js-open-overlay-image").on( "click", function(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
var url = $(this).attr('href');
|
||||
showOverlay('<img src="' + url + '"/>');
|
||||
});
|
||||
|
||||
{% if project.extension_props.cloud.video_url %}
|
||||
//- By isherwood - http://jsfiddle.net/isherwood/cH6e8/
|
||||
function getYoutubeId(url) {
|
||||
var regExp = /^.*(youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|\&v=)([^#\&\?]*).*/;
|
||||
var match = url.match(regExp);
|
||||
|
||||
if (match && match[2].length == 11) {
|
||||
return match[2];
|
||||
} else {
|
||||
return 'error';
|
||||
}
|
||||
}
|
||||
|
||||
var videoId = getYoutubeId('{{ project.extension_props.cloud.video_url }}');
|
||||
var iframeMarkup = '<iframe width="960" height="540" src="//www.youtube.com/embed/'
|
||||
+ videoId + '" frameborder="0" allowfullscreen></iframe>';
|
||||
|
||||
$("a.js-open-overlay-video").on( "click", function(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
showOverlay(iframeMarkup);
|
||||
});
|
||||
{% endif %}
|
||||
|
||||
//- Click anywhere on the page or hit Escape to hide the overlay.
|
||||
$(document).click(function () {
|
||||
hideOverlay();
|
||||
});
|
||||
@@ -165,15 +186,4 @@ script.
|
||||
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 %}
|
||||
|
@@ -1,11 +1,14 @@
|
||||
| {% extends 'layout.html' %}
|
||||
| {% from '_macros/_add_new_menu.html' import add_new_menu %}
|
||||
| {% from 'projects/_macros.html' import render_secondary_navigation %}
|
||||
| {% from '_macros/_navigation.html' import navigation_project %}
|
||||
|
||||
include ../../../../pillar/src/templates/mixins/components
|
||||
|
||||
| {% block page_title %}{{ project.name }}{% endblock%}
|
||||
|
||||
| {% if title is not defined %}
|
||||
| {% set title = 'project' %}
|
||||
| {% endif %}
|
||||
|
||||
| {% block og %}
|
||||
meta(property="og:type", content="website")
|
||||
@@ -82,67 +85,41 @@ script(src="{{ url_for('static_pillar', filename='assets/js/video_plugins.min.js
|
||||
| {% endblock %}
|
||||
|
||||
| {% 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")
|
||||
| {% endblock %}
|
||||
|
||||
| {% block navigation_tabs %}
|
||||
| {{ render_secondary_navigation(project, navigation_links, title) }}
|
||||
| {{ navigation_project(project, navigation_links, extension_sidebar_links, title) }}
|
||||
| {% endblock navigation_tabs %}
|
||||
|
||||
| {% block body %}
|
||||
#project-container
|
||||
#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-container.is-sidebar-visible
|
||||
#project-side-container.bg-light
|
||||
#project_nav(class="{{ title }}")
|
||||
#project_nav-container
|
||||
| {% 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 %}
|
||||
#project_tree.bg-light.px-1.py-2.border-right
|
||||
#project_tree.bg-light.p-1
|
||||
| {% endblock project_tree %}
|
||||
| {% endif %}
|
||||
|
||||
|
||||
#project_context-container
|
||||
.breadcrumbs-container.bg-dark.fixed-top
|
||||
button.project-sidebar-toggle.btn.btn-sm.btn-link.px-1.bg-dark.rounded-0.text-muted(
|
||||
type="button",
|
||||
class="js-project-sidebar-toggle")
|
||||
i.pi-menu
|
||||
|
||||
node-breadcrumbs(node-id="{{ node._id }}", @navigate="(nodeId)=>{displayNode(nodeId)}")
|
||||
script.
|
||||
new Vue({el:'node-breadcrumbs'});
|
||||
|
||||
| {% if project.has_method('PUT') %}
|
||||
#project_context-header.position-fixed
|
||||
#project_context-header.position-absolute
|
||||
ul.project-edit-tools.disabled.d-flex.list-unstyled.py-2.mb-0
|
||||
li.dropdown(
|
||||
title="Create...",
|
||||
@@ -263,7 +240,8 @@ link(href="{{ url_for('static_cloud', filename='assets/css/project-main.css') }}
|
||||
| {% endif %}
|
||||
#project_context
|
||||
| {% block project_context %}
|
||||
| {% if show_project %}
|
||||
| {% if show_project and not browse %}
|
||||
| {# Embed the project view only if we are not exploring it. #}
|
||||
| {% include "projects/view_embed.html" %}
|
||||
| {% endif %}
|
||||
| {% endblock project_context %}
|
||||
@@ -292,6 +270,13 @@ script(type="text/javascript", src="{{ url_for('static_pillar', filename='assets
|
||||
| {% endif %}
|
||||
|
||||
script.
|
||||
loadProjectSidebar();
|
||||
|
||||
$('body').on('click', '.js-project-sidebar-toggle', function(e){
|
||||
e.preventDefault();
|
||||
toggleProjectSidebar();
|
||||
});
|
||||
|
||||
function updateToggleProjHeaderMenuItem() {
|
||||
var $toggle_projheader = $('#item_toggle_projheader');
|
||||
|
||||
@@ -349,7 +334,7 @@ script.
|
||||
|
||||
// TODO: Maybe remove this, now it's also in loadNodeContent(), but double-check
|
||||
// it's done like that in all users of updateUi().
|
||||
$('.loader-bar').removeClass('active');
|
||||
loadingBarHide();
|
||||
}
|
||||
| {% endblock %}
|
||||
|
||||
@@ -377,31 +362,36 @@ script.
|
||||
|
||||
|
||||
function loadNodeContent(url, nodeId) {
|
||||
$('.loader-bar').addClass('active');
|
||||
|
||||
var $projectContext = $('#project_context')
|
||||
$projectContext.trigger('pillar:workStart')
|
||||
|
||||
$.get(url, function(dataHtml) {
|
||||
// Update the DOM injecting the generate HTML into the page
|
||||
$('#project_context').html(dataHtml);
|
||||
$projectContext.html(dataHtml);
|
||||
})
|
||||
.done(function(){
|
||||
pillar.events.Nodes.triggerLoaded(nodeId);
|
||||
updateUi(nodeId, 'view');
|
||||
})
|
||||
.fail(function(dataResponse) {
|
||||
$('#project_context').html($('<iframe id="server_error"/>'));
|
||||
$projectContext.html($('<iframe id="server_error"/>'));
|
||||
$('#server_error').attr('src', url);
|
||||
})
|
||||
.always(function(){
|
||||
$('.loader-bar').removeClass('active');
|
||||
$projectContext.trigger('pillar:workStop')
|
||||
$('.button-edit-icon').addClass('pi-edit').removeClass('pi-spin spin');
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function loadProjectContent(url) {
|
||||
$('.loader-bar').addClass('active');
|
||||
var $projectContext = $('#project_context')
|
||||
$projectContext.trigger('pillar:workStart')
|
||||
|
||||
$.get(url, function(dataHtml) {
|
||||
// Update the DOM injecting the generated HTML into the page
|
||||
$('#project_context').html(dataHtml);
|
||||
$projectContext.html(dataHtml);
|
||||
})
|
||||
.done(function() {
|
||||
updateUi('', 'view');
|
||||
@@ -409,11 +399,11 @@ script.
|
||||
addMenuDisable(['texture']);
|
||||
})
|
||||
.fail(function(dataResponse) {
|
||||
$('#project_context').html($('<iframe id="server_error"/>'));
|
||||
$projectContext.html($('<iframe id="server_error"/>'));
|
||||
$('#server_error').attr('src', url);
|
||||
})
|
||||
.always(function(){
|
||||
$('.loader-bar').removeClass('active');
|
||||
$projectContext.trigger('pillar:workStop')
|
||||
$('.button-edit-icon').addClass('pi-edit').removeClass('pi-spin spin');
|
||||
});
|
||||
}
|
||||
@@ -507,6 +497,11 @@ script.
|
||||
}
|
||||
$('.project-mode-view').displayAs('inline-block');
|
||||
$('.project-mode-edit').hide();
|
||||
|
||||
{% if browse %}
|
||||
let url = "{{url_for('cloud.project_browse_view_nodes', project_url=project.url)}}";
|
||||
loadProjectContent(url);
|
||||
{% endif %}
|
||||
} else {
|
||||
displayNode(nodeId, false);
|
||||
}
|
||||
|
@@ -1,40 +1,23 @@
|
||||
| {% extends 'layout.html' %}
|
||||
| {% from '_macros/_navigation.html' import navigation_collection %}
|
||||
| {% from '_macros/_navigation.html' import navigation_homepage %}
|
||||
| {% from '_macros/_opengraph.html' import opengraph %}
|
||||
|
||||
include ../../../pillar/src/templates/mixins/components
|
||||
include mixins/components
|
||||
|
||||
| {# 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.' %}
|
||||
| {# Default collection is 'Courses' #}
|
||||
| {% set page_title = 'Courses' %}
|
||||
| {% set page_description = 'In-depth training on character modeling, 3D printing, rigging, VFX and more.' %}
|
||||
| {% set page_header_image = url_for('static', filename='assets/img/backgrounds/background_agent327_04.jpg', _external=True) %}
|
||||
|
||||
| {% if title == 'courses' %}
|
||||
| {% set page_title = 'Courses' %}
|
||||
| {% set page_description = 'Production quality training by 3D professionals' %}
|
||||
| {% set page_header_image = url_for('static', filename='assets/img/backgrounds/background_agent327_04.jpg') %}
|
||||
| {% set page_header_text = 'Character modeling, 3D printing, VFX, rigging and more.' %}
|
||||
|
||||
| {% elif title == 'workshops' %}
|
||||
| {% if title == 'workshops' %}
|
||||
| {% set page_title = 'Workshops' %}
|
||||
| {% set page_description = 'Production quality training by 3D professionals' %}
|
||||
| {% set page_header_image = url_for('static', filename='assets/img/backgrounds/background_agent327_04.jpg') %}
|
||||
| {% set page_header_text = 'Enter the artist workshop and learn by example.' %}
|
||||
|
||||
| {% set page_description = 'Enter the artist workshop and learn by example.' %}
|
||||
| {% set page_header_image = url_for('static', filename='assets/img/backgrounds/background_agent327_04.jpg', _external=True) %}
|
||||
| {% endif %}
|
||||
|
||||
| {% block og %}
|
||||
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 }}")
|
||||
| {{ opengraph(page_title, page_description, page_header_image, request.url) }}
|
||||
| {% endblock %}
|
||||
|
||||
| {% block page_title %}
|
||||
@@ -42,69 +25,43 @@ meta(name="twitter:image", content="{{ page_header_image }}")
|
||||
| {% endblock %}
|
||||
|
||||
| {% block navigation_tabs %}
|
||||
| {{ navigation_collection(title) }}
|
||||
| {{ navigation_homepage(title) }}
|
||||
| {% endblock navigation_tabs %}
|
||||
|
||||
| {% block body %}
|
||||
.container.py-4
|
||||
+category_list_header('{{ page_title }}', '{{ page_description }}', '{{ request.url }}')
|
||||
|
||||
| {# 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")
|
||||
+card-deck()
|
||||
| {% for project in projects %}
|
||||
|
||||
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
|
||||
| {% if (project.status == 'published') or (project.status == 'pending' and current_user.is_authenticated) and project._id != config.MAIN_PROJECT_ID %}
|
||||
+card(
|
||||
class='js-project-go card-fade cursor-pointer mb-4',
|
||||
style="min-width: 30%",
|
||||
data-url="{{ url_for('projects.view', project_url=project.url) }}",
|
||||
tabindex='{{ loop.index }}')
|
||||
|
||||
.container.pb-5
|
||||
.row
|
||||
.col-12
|
||||
.pt-4
|
||||
h2.text-uppercase.font-weight-bold
|
||||
| {{ page_title }}
|
||||
.lead
|
||||
| {{ page_header_text }}
|
||||
| {% if project.picture_16_9 %}
|
||||
a.card-thumbnail(href="{{ url_for('projects.view', project_url=project.url) }}")
|
||||
img.card-img-top(
|
||||
alt="{{ project.name }}",
|
||||
src="{{ project.picture_16_9.thumbnail('l', api=api) }}")
|
||||
| {% endif %}
|
||||
|
||||
hr.pb-2
|
||||
|
||||
+card-deck(3)
|
||||
| {% 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 %}
|
||||
+card(
|
||||
class='js-project-go card-fade cursor-pointer mb-4 mx-0',
|
||||
data-url="{{ url_for('projects.view', project_url=project.url) }}",
|
||||
tabindex='{{ loop.index }}')
|
||||
| {% if project.picture_header %}
|
||||
a(href="{{ url_for('projects.view', project_url=project.url) }}")
|
||||
img.card-img-top(
|
||||
src="{{ project.picture_header.thumbnail('l', api=api) }}", alt="{{ project.name }}")
|
||||
.card-body
|
||||
h5.card-title
|
||||
| {{ project.name }}
|
||||
| {% if project.status == 'pending' and current_user.is_authenticated and current_user.has_role('admin') %}
|
||||
small (pending)
|
||||
| {% endif %}
|
||||
|
||||
.card-body
|
||||
h5.card-title
|
||||
| {{ project.name }}
|
||||
| {% if project.status == 'pending' and current_user.is_authenticated and current_user.has_role('admin') %}
|
||||
small (pending)
|
||||
| {% endif %}
|
||||
|
||||
| {% if project.summary %}
|
||||
p.card-text
|
||||
| {{project.summary|safe}}
|
||||
| {% endif %}
|
||||
| {% if project.summary %}
|
||||
p.card-text
|
||||
| {{project.summary|safe}}
|
||||
| {% endif %}
|
||||
| {% endif %}
|
||||
| {% endfor %}
|
||||
| {% endif %}
|
||||
| {% endfor %}
|
||||
|
||||
| {% endblock %}
|
||||
|
||||
|
@@ -1,20 +1,19 @@
|
||||
| {% extends 'layout.html' %}
|
||||
| {% from '_macros/_navigation.html' import navigation_homepage %}
|
||||
|
||||
| {% block page_title %}Services{% endblock %}
|
||||
| {% set title = 'services' %}
|
||||
| {% from '_macros/_opengraph.html' import opengraph %}
|
||||
include ../../../pillar/src/templates/mixins/components
|
||||
include mixins/components
|
||||
|
||||
| {% set title = 'services' %}
|
||||
|
||||
| {% set page_title = 'Services' %}
|
||||
| {% set page_description = 'On Blender Cloud you can create and share personal projects, access our texture and HDRI library (or create your own), keep track of your production, manage your renders and much more!' %}
|
||||
| {% set page_header_image = url_for('static', filename='assets/img/backgrounds/background_services_16_9.jpg', _external=true) %}
|
||||
|
||||
| {% block page_title %}{{ page_title }}{% endblock %}
|
||||
|
||||
| {% block og %}
|
||||
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')}}")
|
||||
| {{ opengraph(page_title, page_description, page_header_image, request.url) }}
|
||||
| {% endblock %}
|
||||
|
||||
| {% block navigation_tabs %}
|
||||
@@ -27,8 +26,8 @@ meta(name="twitter:image", content="{{ url_for('static', filename='assets/img/ba
|
||||
| {% endblock %}
|
||||
|
||||
| {% block body %}
|
||||
- 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!";
|
||||
+jumbotron("Services", header_text, "{{ url_for('static', filename='assets/img/backgrounds/services_projects.jpg')}}")(class="jumbotron-overlay")
|
||||
.container.py-4
|
||||
+category_list_header('{{ page_title }}', '{{ page_description }}', '{{ request.url }}')
|
||||
|
||||
- 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
|
||||
@@ -46,15 +45,18 @@ section#blender-cloud-add-on.page-card
|
||||
small Blender Cloud add-on requires Blender 2.78 or newer
|
||||
|
||||
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
|
||||
| Download add-on <small>v</small> {{ config.BLENDER_CLOUD_ADDON_VERSION }}
|
||||
|
||||
.page-card-side
|
||||
a.page-card-side(
|
||||
href="/r/downloads/blender_cloud-latest-addon.zip",
|
||||
title="Download Blender Cloud add-on")
|
||||
img(
|
||||
src="{{ url_for('static', filename='assets/img/features/blender_cloud_addon_thumbnail.png')}}")
|
||||
|
||||
section#blender-sync.page-card.right
|
||||
section#blender-sync.page-card
|
||||
.page-card-side
|
||||
h2.page-card-title Blender Sync
|
||||
.page-card-summary
|
||||
@@ -68,21 +70,25 @@ section#blender-sync.page-card.right
|
||||
.tip !{addon_text}
|
||||
|
||||
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
|
||||
| Download add-on <small>v</small> {{ config.BLENDER_CLOUD_ADDON_VERSION }}
|
||||
|
||||
a.btn.btn-link(
|
||||
href="/blog/introducing-blender-sync")
|
||||
href="/blog/introducing-blender-sync",
|
||||
title="Learn more about Blender Sync")
|
||||
| Learn More
|
||||
i.pi-angle-right
|
||||
|
||||
.page-card-side
|
||||
a.page-card-side(
|
||||
href="/blog/introducing-blender-sync",
|
||||
title="Learn more about Blender Sync")
|
||||
img(
|
||||
src="{{ url_for('static', filename='assets/img/features/sync_thumbnail.jpg')}}")
|
||||
|
||||
|
||||
section#texture-browser.page-card.right
|
||||
section#texture-browser.page-card
|
||||
.page-card-side
|
||||
h2.page-card-title Texture & HDRI Browser
|
||||
.page-card-summary
|
||||
@@ -95,16 +101,21 @@ section#texture-browser.page-card.right
|
||||
|
||||
a.btn.btn-outline-primary.js-watch-video(
|
||||
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
|
||||
| Watch Video
|
||||
|
||||
.page-card-side
|
||||
a.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(
|
||||
src="{{ url_for('static', filename='assets/img/features/tex_library_thumbnail.jpg')}}")
|
||||
|
||||
|
||||
section#image-sharing.page-card.right
|
||||
section#image-sharing.page-card
|
||||
.page-card-side
|
||||
h2.page-card-title Image Sharing
|
||||
.page-card-summary
|
||||
@@ -115,21 +126,27 @@ section#image-sharing.page-card.right
|
||||
|
||||
a.btn.btn-outline-primary.js-watch-video(
|
||||
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
|
||||
| Watch Video
|
||||
|
||||
a.btn.btn-link(
|
||||
href="/blog/introducing-image-sharing")
|
||||
href="/blog/introducing-image-sharing",
|
||||
title="Learn more about Image Sharing")
|
||||
| Learn More
|
||||
i.pi-angle-right
|
||||
|
||||
.page-card-side
|
||||
a.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(
|
||||
src="{{ url_for('static', filename='assets/img/features/image_sharing_thumbnail.jpg')}}")
|
||||
|
||||
|
||||
section#projects.page-card.right
|
||||
section#projects.page-card
|
||||
.page-card-side
|
||||
h2.page-card-title Private Projects
|
||||
.page-card-summary.
|
||||
@@ -137,16 +154,19 @@ section#projects.page-card.right
|
||||
Upload assets and collaborate with other Blender Cloud members.
|
||||
|
||||
a.btn.btn-link(
|
||||
href="/blog/introducing-private-projects")
|
||||
href="/blog/introducing-private-projects",
|
||||
title="Learn more about Private Projects")
|
||||
| Learn More
|
||||
i.pi-angle-right
|
||||
|
||||
.page-card-side
|
||||
a.page-card-side(
|
||||
href="/blog/introducing-private-projects",
|
||||
title="Learn more about Private Projects")
|
||||
img(
|
||||
src="{{ url_for('static', filename='assets/img/features/projects_thumbnail.jpg')}}")
|
||||
|
||||
|
||||
section#attract.page-card.right
|
||||
section#attract.page-card
|
||||
.page-card-side
|
||||
h2.page-card-title
|
||||
| Attract
|
||||
@@ -165,12 +185,14 @@ section#attract.page-card.right
|
||||
| Learn More
|
||||
i.pi-angle-right
|
||||
|
||||
.page-card-side
|
||||
a.page-card-side(
|
||||
href="/blog/attract-and-flamenco-public-beta",
|
||||
title="Learn more about Attract")
|
||||
img(
|
||||
src="{{ url_for('static', filename='assets/img/features/attract_thumbnail.jpg')}}")
|
||||
|
||||
|
||||
section#flamenco.page-card.right
|
||||
section#flamenco.page-card
|
||||
.page-card-side
|
||||
h2.page-card-title
|
||||
| Flamenco
|
||||
@@ -189,7 +211,9 @@ section#flamenco.page-card.right
|
||||
| Learn More
|
||||
i.pi-angle-right
|
||||
|
||||
.page-card-side
|
||||
a.page-card-side(
|
||||
href="https://flamenco.io",
|
||||
title="Learn more about Flamenco")
|
||||
img(
|
||||
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
|
||||
Blender Cloud Service.
|
||||
p.
|
||||
Blender Cloud is an activity of Blender Institute B.V. - Entrepotdok 57A - 1018 AD Amsterdam
|
||||
- the Netherlands, contact: institute@blender.org.
|
||||
Blender Cloud is an activity of Blender Institute B.V. - Buikslotermeerplein 161 -
|
||||
1025 ET Amsterdam - the Netherlands, contact: institute@blender.org.
|
||||
p.
|
||||
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
|
||||
@@ -57,10 +57,10 @@ style.
|
||||
h2 Blender Cloud Membership fee
|
||||
p.
|
||||
To register and activate a membership an additional charge will apply, including a minimum of
|
||||
3 months of membership fees. Fees are: (Feb 23, 2014)
|
||||
1 month of membership fees. Fees are: (Apr 09, 2020)
|
||||
p.
|
||||
45 euro (59 USD), membership registration, which includes 3 months Cloud membership.
|
||||
After that, 10 euro (13.50 USD), monthly membership fee
|
||||
9.90 EUR/month for subscription with automatic renewal and 14.90 EUR/month for subscriptions
|
||||
with manual renewal.
|
||||
|
||||
h2 Refund policy
|
||||
p.
|
||||
|
@@ -29,52 +29,115 @@ meta(property="og:image", content="{{ url_for('static', filename='assets/img/bac
|
||||
li.pr-1
|
||||
| {% if current_user.is_anonymous %}
|
||||
a.btn.btn-sm.btn-outline-primary.px-3(href="{{ url_for('users.login', next='/') }}")
|
||||
| Log in & Explore
|
||||
| Log in & Browse
|
||||
| {% else %}
|
||||
a.btn.btn-sm.btn-outline-primary.px-3(href="{{ url_for('main.homepage') }}")
|
||||
| Explore
|
||||
| Browse
|
||||
| {% endif %}
|
||||
| {% endblock navigation_user %}
|
||||
|
||||
| {% block body %}
|
||||
#page-container.join
|
||||
#page-header(
|
||||
style="background-image: url({{ url_for('static', filename='assets/img/backgrounds/background_dweebs_01.jpg')}})")
|
||||
style="background-image: url({{ url_for('static', filename='assets/img/backgrounds/background_spring_02.jpg')}})")
|
||||
.container.wide-on-sm
|
||||
.row
|
||||
.col-sm-8.col-md-6.col-lg-6
|
||||
.page-title
|
||||
| Open content #[br] creation platform
|
||||
| Blender Cloud
|
||||
|
||||
.page-title-summary.
|
||||
Support a world-class 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.
|
||||
Join the production platform as used daily by a world-class
|
||||
team of artists and developers.
|
||||
|
||||
#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
|
||||
a(href="{{ subscribe_url }}")
|
||||
h2 Subscribe to Get
|
||||
a(href="{{ url_for('cloud.courses') }}")
|
||||
h2 Featured Content
|
||||
|
||||
.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(
|
||||
@@ -111,69 +174,6 @@ li.pr-1
|
||||
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(
|
||||
style="background-image: url({{ url_for('static', filename='assets/img/backgrounds/background_blue_01.jpg')}})")
|
||||
|
BIN
static/assets/img/backgrounds/background_services_16_9.jpg
Normal file
After Width: | Height: | Size: 116 KiB |
BIN
static/assets/img/backgrounds/background_spring_02.jpg
Normal file
After Width: | Height: | Size: 138 KiB |
BIN
static/assets/img/features/coffee_run_01.jpg
Normal file
After Width: | Height: | Size: 48 KiB |
BIN
static/assets/img/features/coffee_run_02.jpg
Normal file
After Width: | Height: | Size: 37 KiB |
BIN
static/assets/img/features/gallery_01.jpg
Normal file
After Width: | Height: | Size: 71 KiB |
BIN
static/assets/img/features/hdri_02.jpg
Normal file
After Width: | Height: | Size: 90 KiB |
BIN
static/assets/img/features/open_movies_spring_02.jpg
Normal file
After Width: | Height: | Size: 52 KiB |
BIN
static/assets/img/features/open_movies_spring_03.jpg
Normal file
After Width: | Height: | Size: 75 KiB |
BIN
static/assets/img/features/settlers_01.jpg
Normal file
After Width: | Height: | Size: 75 KiB |
BIN
static/assets/img/features/textures_02.jpg
Normal file
After Width: | Height: | Size: 96 KiB |
BIN
static/assets/img/features/training_anglerfish_01.jpg
Normal file
After Width: | Height: | Size: 47 KiB |
After Width: | Height: | Size: 24 KiB |
BIN
static/assets/img/features/training_grease_pencil.jpg
Normal file
After Width: | Height: | Size: 57 KiB |
BIN
static/assets/img/features/training_grease_pencil_02.jpg
Normal file
After Width: | Height: | Size: 139 KiB |
BIN
static/assets/img/features/training_procedural_shading_01.jpg
Normal file
After Width: | Height: | Size: 58 KiB |
BIN
static/assets/img/features/training_speed_sculpting.jpg
Normal file
After Width: | Height: | Size: 39 KiB |
BIN
static/assets/img/features/training_speed_sculpting_02.jpg
Normal file
After Width: | Height: | Size: 129 KiB |
BIN
static/assets/img/features/training_speed_sculpting_03.jpg
Normal file
After Width: | Height: | Size: 55 KiB |
After Width: | Height: | Size: 27 KiB |
BIN
static/assets/img/features/training_weight_painting_01.jpg
Normal file
After Width: | Height: | Size: 38 KiB |
BIN
static/assets/img/projects/spring_sidebar_01.jpg
Normal file
After Width: | Height: | Size: 43 KiB |