Compare commits
84 Commits
last-outsi
...
wip-redesi
Author | SHA1 | Date | |
---|---|---|---|
b12b320cf0 | |||
95e51e90de | |||
92106459a0 | |||
982047fc3b | |||
5a36888f61 | |||
59e0adf3ca | |||
c25df6f0ad | |||
813750a006 | |||
9ba2735c8c | |||
73e8a81f3c | |||
9ffcde3348 | |||
f0b18e88f4 | |||
ed211f9473 | |||
7a4c7d75f6 | |||
cc16351136 | |||
7dc1e6f9a1 | |||
fcf715b5b1 | |||
68d09dc886 | |||
169a7f51f0 | |||
f48a4883ae | |||
01b6693324 | |||
012ba06655 | |||
2be601d0b0 | |||
4dc11b075a | |||
4696d09fed | |||
663cf7bf2d | |||
4e8530478a | |||
b2d10b5ca7 | |||
fad1aa75e9 | |||
d9cf2d8631 | |||
34cb45d5f1 | |||
3c74e79e4a | |||
fb2f245f1e | |||
8c6fa1e423 | |||
b66b6cf445 | |||
b153cae70e | |||
368e3f6d40 | |||
e9ec850d90 | |||
280d26801e | |||
6c285c078f | |||
debfc4e356 | |||
541663ce0c | |||
1fb044e7c1 | |||
4769e2e904 | |||
cf99383b9c | |||
8613ac7244 | |||
8c48a61114 | |||
0259c5e0ec | |||
a726fd1fbe | |||
df71738623 | |||
6a698daaa0 | |||
7f538bdaee | |||
5f07c7ce17 | |||
5a42e2dcb8 | |||
d5a54b7cf1 | |||
7cb4b37ae2 | |||
1fca473257 | |||
5a6035a494 | |||
98698be7eb | |||
d4f072480c | |||
2d036ee657 | |||
1bb762db6b | |||
11743c54e2 | |||
29d1d02bfd | |||
f7e5db2174 | |||
2141aed06c | |||
6b56df9e9c | |||
5a4519659a | |||
c3ddc831aa | |||
1a63b51c48 | |||
484ac34c50 | |||
908360eb1c | |||
3bf1c3ea1b | |||
fc58fbef5b | |||
9e961580d3 | |||
87cf5a9844 | |||
5a3a7a3883 | |||
3be926b9b3 | |||
ff5af22771 | |||
6f73222dcd | |||
1617a119db | |||
bef402a6b0 | |||
94ef616593 | |||
ffc4f271e8 |
5
.gitignore
vendored
@@ -6,6 +6,7 @@ __pycache__
|
||||
|
||||
/cloud/templates/
|
||||
/cloud/static/assets/css/
|
||||
/cloud/static/assets/js/bootstrap.min.js
|
||||
node_modules/
|
||||
|
||||
/config_local.py
|
||||
@@ -18,3 +19,7 @@ node_modules/
|
||||
/google_app*.json
|
||||
/docker/2_buildpy/python/
|
||||
/docker/4_run/wheelhouse/
|
||||
/docker/4_run/deploy/
|
||||
/celerybeat-schedule.bak
|
||||
/celerybeat-schedule.dat
|
||||
/celerybeat-schedule.dir
|
||||
|
140
README.md
@@ -3,45 +3,95 @@
|
||||
Welcome to the [Blender Cloud](https://cloud.blender.org/) code repo!
|
||||
Blender Cloud runs on the [Pillar](https://pillarframework.org/) framework.
|
||||
|
||||
## Quick setup
|
||||
Set up a node with these commands.
|
||||
## Development setup
|
||||
Jumpstart Blender Cloud development with this simple guide.
|
||||
|
||||
### System setup
|
||||
Blender Cloud relies on a number of services in order to run. Check out the [Pillar system setup](
|
||||
https://pillarframework.org/development/system_setup/#step-by-step-setup) to set this up.
|
||||
|
||||
Add `cloud.local` to the `/etc/hosts` file on localhost. This is a development convention.
|
||||
|
||||
### Check out the code
|
||||
Go to the local development directory and check out the following repositories, next to each other.
|
||||
|
||||
```
|
||||
#!/usr/bin/env bash
|
||||
|
||||
sudo mkdir -p /data/{git,storage,config,certs}
|
||||
sudo apt-get update
|
||||
sudo apt-get -y install python3-pip
|
||||
pip3 install docker-compose
|
||||
|
||||
cd /data/git
|
||||
cd /home/guest/Developer
|
||||
git clone git://git.blender.org/pillar-python-sdk.git
|
||||
git clone git://git.blender.org/pillar.git -b production
|
||||
git clone git://git.blender.org/attract.git -b production
|
||||
git clone git://git.blender.org/flamenco.git -b production
|
||||
git clone git://git.blender.org/blender-cloud.git -b production
|
||||
git clone https://github.com/armadillica/grafista.git -b production
|
||||
|
||||
echo '0 8 * * * root docker exec -d grafista bash manage.sh collect' > /etc/cron.d/grafista
|
||||
|
||||
git clone git://git.blender.org/pillar.git
|
||||
git clone git://git.blender.org/attract.git
|
||||
git clone git://git.blender.org/flamenco.git
|
||||
git clone git://git.blender.org/pillar-svnman.git
|
||||
git clone git://git.blender.org/blender-cloud.git
|
||||
```
|
||||
|
||||
After these commands, run `deploy.sh` to build the static files and deploy
|
||||
those too (see below).
|
||||
### Initial setup and configuration
|
||||
|
||||
Create a virtualenv for the project and install the requirements:
|
||||
|
||||
```
|
||||
cd blender-cloud
|
||||
mkvirtualenv blender-cloud -p python3.6
|
||||
pip install -r requirements-dev.txt
|
||||
```
|
||||
|
||||
Build assets and templates for all Blender Cloud dependencies using Gulp.
|
||||
|
||||
```
|
||||
./gulp all
|
||||
```
|
||||
|
||||
Make a copy of the config_local example, which will be further edited as the application is
|
||||
configured.
|
||||
|
||||
```
|
||||
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
|
||||
```
|
||||
|
||||
The command will return the following message:
|
||||
|
||||
```
|
||||
Created project <project_id> for user <user_id>
|
||||
```
|
||||
|
||||
Copy the value of `<project_id>` and assign it as value for `MAIN_PROJECT_ID`.
|
||||
|
||||
Run the application:
|
||||
|
||||
```
|
||||
./manage.py runserver
|
||||
```
|
||||
|
||||
|
||||
## Deploying to production server
|
||||
## Development
|
||||
|
||||
First of all, add those aliases to the `[alias]` section of your `~/.gitconfig`
|
||||
When ready to commit, change the remotes to work over SSH. For example:
|
||||
|
||||
`git remote set-url origin git@git.blender.org:blender-cloud.git`
|
||||
|
||||
For more information, check out [this guide](https://wiki.blender.org/wiki/Tools/Git#Commit_Access).
|
||||
|
||||
|
||||
## Preparing the production branch for deployment
|
||||
|
||||
All revisions to deploy to production should be on the `production` branches of all the relevant
|
||||
repositories.
|
||||
|
||||
Make sure you have these aliases in the `[alias]` section of your `~/.gitconfig`:
|
||||
|
||||
```
|
||||
prod = "!git checkout production && git fetch origin production && gitk --all"
|
||||
ff = "merge --ff-only"
|
||||
pp = "!git push && if [ -e deploy.sh ]; then ./deploy.sh; fi && git checkout master"
|
||||
```
|
||||
|
||||
The following commands should be executed for each subproject; specifically for
|
||||
Pillar and Attract:
|
||||
The following commands should be executed for each (sub)project; specifically for
|
||||
the current repository, Pillar, Attract, Flamenco, and Pillar-SVNMan:
|
||||
|
||||
```
|
||||
cd $projectdir
|
||||
@@ -64,35 +114,19 @@ git ff master
|
||||
# Run tests again
|
||||
py.test
|
||||
|
||||
# Push the production branch and run dummy deploy script.
|
||||
git pp # pp = "Push to Production"
|
||||
|
||||
# The above alias waits for [ENTER] until all deploys are done.
|
||||
# Let it wait, perform the other commands in another terminal.
|
||||
# Push the production branch.
|
||||
git push
|
||||
```
|
||||
|
||||
Now follow the above receipe on the Blender Cloud project as well.
|
||||
Contrary to the subprojects, `git pp` will actually perform the deploy
|
||||
for real.
|
||||
## Deploying to production server
|
||||
|
||||
Now you can press `[ENTER]` in the Pillar, Attract, and Flamenco terminals
|
||||
that were still waiting for it.
|
||||
```
|
||||
workon blender-cloud # activate your virtualenv
|
||||
cd $projectdir/deploy
|
||||
./2docker.sh
|
||||
./build-all.sh # or ./build-quick.sh
|
||||
./2server.sh servername
|
||||
```
|
||||
|
||||
After everything is done, your (sub)projects should all be back on
|
||||
the master branch.
|
||||
|
||||
|
||||
## Updating dependencies via Docker images
|
||||
|
||||
To update dependencies that need compiling, you need the `2_build` docker
|
||||
container. To rebuild the lot, run `docker/build.sh`.
|
||||
|
||||
Follow these steps to deploy the new container on production:
|
||||
|
||||
1. run `docker/build.sh`
|
||||
2. `docker push armadillica/blender_cloud`
|
||||
|
||||
On the production machine:
|
||||
|
||||
1. `docker pull armadillica/blender_cloud`
|
||||
2. `docker-compose up -d` (from the `/data/git/blender-cloud/docker` directory)
|
||||
To deploy another branch than `production`, do `export DEPLOY_BRANCH=otherbranch` before starting
|
||||
the above commands.
|
||||
|
@@ -17,6 +17,8 @@ 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
|
||||
|
||||
|
||||
blueprint = Blueprint('cloud', __name__)
|
||||
@@ -62,15 +64,6 @@ def _homepage_context() -> dict:
|
||||
post.picture = get_file(post.picture, api=api)
|
||||
post.url = url_for_node(node=post)
|
||||
|
||||
# Render attachments
|
||||
try:
|
||||
post_contents = post['properties']['content']
|
||||
except KeyError:
|
||||
log.warning('Blog post %s has no content', post._id)
|
||||
else:
|
||||
post['properties']['content'] = pillar.web.nodes.attachments.render_attachments(
|
||||
post, post_contents)
|
||||
|
||||
# Get latest assets added to any project
|
||||
latest_assets = Node.latest('assets', api=api)
|
||||
|
||||
@@ -443,6 +436,32 @@ def comments_for_node(node_id):
|
||||
return render_comments_for_node(node_id, can_post_comments=can_post_comments)
|
||||
|
||||
|
||||
@blueprint.route('/p/hero')
|
||||
def project_hero():
|
||||
api = system_util.pillar_api()
|
||||
project = find_project_or_404('hero',
|
||||
embedded={'header_node': 1},
|
||||
api=api)
|
||||
# Load the header video file, if there is any.
|
||||
header_video_file = None
|
||||
header_video_node = None
|
||||
if project.header_node and project.header_node.node_type == 'asset' and \
|
||||
project.header_node.properties.content_type == 'video':
|
||||
header_video_node = project.header_node
|
||||
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)
|
||||
|
||||
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')
|
||||
|
||||
|
||||
def setup_app(app):
|
||||
global _homepage_context
|
||||
cached = app.cache.cached(timeout=300)
|
||||
|
32
config_local.example.py
Normal file
@@ -0,0 +1,32 @@
|
||||
import os
|
||||
|
||||
DEBUG = True
|
||||
|
||||
BLENDER_ID_ENDPOINT = 'http://id.local:8000'
|
||||
|
||||
SERVER_NAME = 'cloud.local:5001'
|
||||
SCHEME = 'http'
|
||||
PILLAR_SERVER_ENDPOINT = f'{SCHEME}://{SERVER_NAME}/api/'
|
||||
|
||||
os.environ['OAUTHLIB_INSECURE_TRANSPORT'] = 'true'
|
||||
os.environ['PILLAR_MONGO_DBNAME'] = 'cloud'
|
||||
os.environ['PILLAR_MONGO_PORT'] = '27017'
|
||||
os.environ['PILLAR_MONGO_HOST'] = 'mongo'
|
||||
|
||||
os.environ['PILLAR_SERVER_ENDPOINT'] = PILLAR_SERVER_ENDPOINT
|
||||
|
||||
SECRET_KEY = '##DEFINE##'
|
||||
|
||||
OAUTH_CREDENTIALS = {
|
||||
'blender-id': {
|
||||
'id': 'CLOUD-OF-SNOWFLAKES-42',
|
||||
'secret': '##DEFINE##',
|
||||
}
|
||||
}
|
||||
|
||||
MAIN_PROJECT_ID = '##DEFINE##'
|
||||
URLER_SERVICE_AUTH_TOKEN = '##DEFINE##'
|
||||
|
||||
ZENCODER_API_KEY = '##DEFINE##'
|
||||
ZENCODER_NOTIFICATIONS_SECRET = '##DEFINE##'
|
||||
ZENCODER_NOTIFICATIONS_URL = 'http://zencoderfetcher/'
|
164
deploy.sh
@@ -1,164 +0,0 @@
|
||||
#!/bin/bash -e
|
||||
|
||||
case $1 in
|
||||
cloud*)
|
||||
DEPLOYHOST="$1"
|
||||
;;
|
||||
*)
|
||||
echo "Use $0 cloud{nr}|cloud.blender.org" >&2
|
||||
exit 1
|
||||
esac
|
||||
|
||||
echo -n "Deploying to ${DEPLOYHOST}... "
|
||||
|
||||
if ! ping ${DEPLOYHOST} -q -c 1 -W 2 >/dev/null; then
|
||||
echo "host ${DEPLOYHOST} cannot be pinged, refusing to deploy." >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
echo "press [ENTER] to continue, Ctrl+C to abort."
|
||||
read dummy
|
||||
|
||||
|
||||
# Deploys the current production branch to the production machine.
|
||||
PROJECT_NAME="blender-cloud"
|
||||
DOCKER_NAME="blender_cloud"
|
||||
CELERY_WORKER_DOCKER_NAME="celery_worker"
|
||||
CELERY_BEAT_DOCKER_NAME="celery_beat"
|
||||
REMOTE_ROOT="/data/git/${PROJECT_NAME}"
|
||||
|
||||
SSH="ssh -o ClearAllForwardings=yes -o PermitLocalCommand=no ${DEPLOYHOST}"
|
||||
|
||||
# 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 "$($readlink -f "$0")")"
|
||||
cd ${ROOT}
|
||||
|
||||
# Check that we're on production branch.
|
||||
if [ $(git rev-parse --abbrev-ref HEAD) != "production" ]; then
|
||||
echo "You are NOT on the production branch, refusing to deploy." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check that production branch has been pushed.
|
||||
if [ -n "$(git log origin/production..production --oneline)" ]; then
|
||||
echo "WARNING: not all changes to the production branch have been pushed."
|
||||
echo "Press [ENTER] to continue deploying current origin/production, CTRL+C to abort."
|
||||
read dummy
|
||||
fi
|
||||
|
||||
function find_module()
|
||||
{
|
||||
MODULE_NAME=$1
|
||||
MODULE_DIR=$(python <<EOT
|
||||
from __future__ import print_function
|
||||
import os.path
|
||||
try:
|
||||
import ${MODULE_NAME}
|
||||
except ImportError:
|
||||
raise SystemExit('${MODULE_NAME} not found on Python path. Are you in the correct venv?')
|
||||
|
||||
print(os.path.dirname(os.path.dirname(${MODULE_NAME}.__file__)))
|
||||
EOT
|
||||
)
|
||||
|
||||
if [ $(git -C $MODULE_DIR rev-parse --abbrev-ref HEAD) != "production" ]; then
|
||||
echo "${MODULE_NAME}: ($MODULE_DIR) NOT on the production branch, refusing to deploy." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo $MODULE_DIR
|
||||
}
|
||||
|
||||
# Find our modules
|
||||
PILLAR_DIR=$(find_module pillar)
|
||||
ATTRACT_DIR=$(find_module attract)
|
||||
FLAMENCO_DIR=$(find_module flamenco)
|
||||
SVNMAN_DIR=$(find_module svnman)
|
||||
|
||||
echo "Pillar : $PILLAR_DIR"
|
||||
echo "Attract : $ATTRACT_DIR"
|
||||
echo "Flamenco: $FLAMENCO_DIR"
|
||||
echo "SVNMan : $SVNMAN_DIR"
|
||||
|
||||
if [ -z "$PILLAR_DIR" -o -z "$ATTRACT_DIR" -o -z "$FLAMENCO_DIR" -o -z "$SVNMAN_DIR" ];
|
||||
then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# SSH to cloud to pull all files in
|
||||
function git_pull() {
|
||||
PROJECT_NAME="$1"
|
||||
BRANCH="$2"
|
||||
REMOTE_ROOT="/data/git/${PROJECT_NAME}"
|
||||
|
||||
echo "==================================================================="
|
||||
echo "UPDATING FILES ON ${PROJECT_NAME}"
|
||||
${SSH} git -C ${REMOTE_ROOT} fetch origin ${BRANCH}
|
||||
${SSH} git -C ${REMOTE_ROOT} log origin/${BRANCH}..${BRANCH} --oneline
|
||||
${SSH} git -C ${REMOTE_ROOT} merge --ff-only origin/${BRANCH}
|
||||
}
|
||||
|
||||
git_pull pillar-python-sdk master
|
||||
git_pull pillar production
|
||||
git_pull attract production
|
||||
git_pull flamenco production
|
||||
git_pull pillar-svnman production
|
||||
git_pull blender-cloud production
|
||||
|
||||
# Update the virtualenv
|
||||
#${SSH} -t docker exec ${DOCKER_NAME} /data/venv/bin/pip install -U -r ${REMOTE_ROOT}/requirements.txt --exists-action w
|
||||
|
||||
# RSync the world
|
||||
$ATTRACT_DIR/rsync_ui.sh ${DEPLOYHOST}
|
||||
$FLAMENCO_DIR/rsync_ui.sh ${DEPLOYHOST}
|
||||
$SVNMAN_DIR/rsync_ui.sh ${DEPLOYHOST}
|
||||
./rsync_ui.sh ${DEPLOYHOST}
|
||||
|
||||
# Notify Sentry of this new deploy.
|
||||
# See https://sentry.io/blender-institute/blender-cloud/settings/release-tracking/
|
||||
# and https://docs.sentry.io/api/releases/post-organization-releases/
|
||||
# and https://sentry.io/api/
|
||||
echo
|
||||
echo "==================================================================="
|
||||
REVISION=$(date +'%Y%m%d.%H%M%S.%Z')
|
||||
echo "Notifying Sentry of this new deploy of revision ${REVISION}."
|
||||
SENTRY_RELEASE_URL="$(${SSH} python3 -c "\"import sys; sys.path.append('${REMOTE_ROOT}'); import config_local; print(config_local.SENTRY_RELEASE_URL)\"")"
|
||||
curl -vs "$SENTRY_RELEASE_URL" -XPOST -H 'Content-Type: application/json' -d "{\"version\": \"$REVISION\"}" | json_pp
|
||||
echo
|
||||
|
||||
# Wait for [ENTER] to restart the server
|
||||
echo
|
||||
echo "==================================================================="
|
||||
echo "NOTE: If you want to edit config_local.py on the server, do so now."
|
||||
echo "NOTE: Press [ENTER] to continue and restart the server process."
|
||||
read dummy
|
||||
echo "Gracefully restarting server process"
|
||||
${SSH} docker exec ${DOCKER_NAME} apache2ctl graceful
|
||||
echo "Server process restarted"
|
||||
|
||||
echo
|
||||
echo "==================================================================="
|
||||
echo "Restarting Celery worker."
|
||||
${SSH} docker restart ${CELERY_WORKER_DOCKER_NAME}
|
||||
echo "Celery worker docker restarted"
|
||||
echo "Restarting Celery beat."
|
||||
${SSH} docker restart ${CELERY_BEAT_DOCKER_NAME}
|
||||
echo "Celery beat docker restarted"
|
||||
|
||||
|
||||
echo
|
||||
echo "==================================================================="
|
||||
echo "Clearing front page from Redis cache."
|
||||
${SSH} docker exec redis redis-cli DEL pwview//
|
||||
|
||||
echo
|
||||
echo "==================================================================="
|
||||
echo "Deploy of ${PROJECT_NAME} is done."
|
||||
echo "==================================================================="
|
116
deploy/2docker.sh
Executable file
@@ -0,0 +1,116 @@
|
||||
#!/bin/bash -e
|
||||
|
||||
DEPLOY_BRANCH=${DEPLOY_BRANCH:-production}
|
||||
|
||||
# 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")")")"
|
||||
DEPLOYDIR="$ROOT/docker/4_run/deploy"
|
||||
PROJECT_NAME="$(basename $ROOT)"
|
||||
|
||||
if [ -e $DEPLOYDIR ]; then
|
||||
echo "$DEPLOYDIR already exists, press [ENTER] to DESTROY AND DEPLOY, Ctrl+C to abort."
|
||||
read dummy
|
||||
rm -rf $DEPLOYDIR
|
||||
else
|
||||
echo -n "Deploying to $DEPLOYDIR… "
|
||||
echo "press [ENTER] to continue, Ctrl+C to abort."
|
||||
read dummy
|
||||
fi
|
||||
|
||||
cd ${ROOT}
|
||||
mkdir -p $DEPLOYDIR
|
||||
REMOTE_ROOT="$DEPLOYDIR/$PROJECT_NAME"
|
||||
|
||||
if [ -z "$SKIP_BRANCH_CHECK" ]; then
|
||||
# Check that we're on production branch.
|
||||
if [ $(git rev-parse --abbrev-ref HEAD) != "$DEPLOY_BRANCH" ]; then
|
||||
echo "You are NOT on the $DEPLOY_BRANCH branch, refusing to deploy." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check that production branch has been pushed.
|
||||
if [ -n "$(git log origin/$DEPLOY_BRANCH..$DEPLOY_BRANCH --oneline)" ]; then
|
||||
echo "WARNING: not all changes to the $DEPLOY_BRANCH branch have been pushed."
|
||||
echo "Press [ENTER] to continue deploying current origin/$DEPLOY_BRANCH, CTRL+C to abort."
|
||||
read dummy
|
||||
fi
|
||||
fi
|
||||
|
||||
function find_module()
|
||||
{
|
||||
MODULE_NAME=$1
|
||||
MODULE_DIR=$(python <<EOT
|
||||
from __future__ import print_function
|
||||
import os.path
|
||||
try:
|
||||
import ${MODULE_NAME}
|
||||
except ImportError:
|
||||
raise SystemExit('${MODULE_NAME} not found on Python path. Are you in the correct venv?')
|
||||
|
||||
print(os.path.dirname(os.path.dirname(${MODULE_NAME}.__file__)))
|
||||
EOT
|
||||
)
|
||||
echo $MODULE_DIR
|
||||
}
|
||||
|
||||
# Find our modules
|
||||
echo "==================================================================="
|
||||
echo "LOCAL MODULE LOCATIONS"
|
||||
PILLAR_DIR=$(find_module pillar)
|
||||
ATTRACT_DIR=$(find_module attract)
|
||||
FLAMENCO_DIR=$(find_module flamenco)
|
||||
SVNMAN_DIR=$(find_module svnman)
|
||||
SDK_DIR=$(find_module pillarsdk)
|
||||
|
||||
echo "Pillar : $PILLAR_DIR"
|
||||
echo "Attract : $ATTRACT_DIR"
|
||||
echo "Flamenco: $FLAMENCO_DIR"
|
||||
echo "SVNMan : $SVNMAN_DIR"
|
||||
echo "SDK : $SDK_DIR"
|
||||
|
||||
if [ -z "$PILLAR_DIR" -o -z "$ATTRACT_DIR" -o -z "$FLAMENCO_DIR" -o -z "$SVNMAN_DIR" -o -z "$SDK_DIR" ];
|
||||
then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
function git_clone() {
|
||||
PROJECT_NAME="$1"
|
||||
BRANCH="$2"
|
||||
LOCAL_ROOT="$3"
|
||||
|
||||
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_clone pillar-python-sdk master $SDK_DIR
|
||||
git_clone pillar $DEPLOY_BRANCH $PILLAR_DIR
|
||||
git_clone attract $DEPLOY_BRANCH $ATTRACT_DIR
|
||||
git_clone flamenco $DEPLOY_BRANCH $FLAMENCO_DIR
|
||||
git_clone pillar-svnman $DEPLOY_BRANCH $SVNMAN_DIR
|
||||
git_clone blender-cloud $DEPLOY_BRANCH $ROOT
|
||||
|
||||
# Gulp everywhere
|
||||
GULP=$ROOT/node_modules/.bin/gulp
|
||||
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
|
||||
|
||||
echo
|
||||
echo "==================================================================="
|
||||
echo "Deploy of ${PROJECT_NAME} is ready for dockerisation."
|
||||
echo "==================================================================="
|
81
deploy/2server.sh
Executable file
@@ -0,0 +1,81 @@
|
||||
#!/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")")")"
|
||||
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"
|
||||
|
||||
#################################################################################
|
||||
case $1 in
|
||||
cloud*)
|
||||
DEPLOYHOST="$1"
|
||||
;;
|
||||
*)
|
||||
echo "Use $0 cloud{nr}|cloud.blender.org" >&2
|
||||
exit 1
|
||||
esac
|
||||
SSH_OPTS="-o ClearAllForwardings=yes -o PermitLocalCommand=no"
|
||||
SSH="ssh $SSH_OPTS $DEPLOYHOST"
|
||||
SCP="scp $SSH_OPTS"
|
||||
|
||||
echo -n "Deploying to $DEPLOYHOST… "
|
||||
|
||||
if ! ping $DEPLOYHOST -q -c 1 -W 2 >/dev/null; then
|
||||
echo "host $DEPLOYHOST cannot be pinged, refusing to deploy." >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
cat <<EOT
|
||||
[ping OK]
|
||||
|
||||
Make sure that you have pushed the $DOCKER_IMAGE
|
||||
docker image to Docker Hub.
|
||||
|
||||
press [ENTER] to continue, Ctrl+C to abort.
|
||||
EOT
|
||||
read dummy
|
||||
|
||||
#################################################################################
|
||||
echo "==================================================================="
|
||||
echo "Bringing remote Docker up to date…"
|
||||
$SSH mkdir -p $REMOTE_DOCKER_COMPOSE_DIR
|
||||
$SCP \
|
||||
$ROOT/docker/{docker-compose.yml,renew-letsencrypt.sh,mongo-backup.{cron,sh}} \
|
||||
$DEPLOYHOST:$REMOTE_DOCKER_COMPOSE_DIR
|
||||
$SSH -T <<EOT
|
||||
set -e
|
||||
cd $REMOTE_DOCKER_COMPOSE_DIR
|
||||
docker pull $DOCKER_IMAGE
|
||||
docker-compose up -d
|
||||
|
||||
echo
|
||||
echo "==================================================================="
|
||||
echo "Clearing front page from Redis cache."
|
||||
docker exec redis redis-cli DEL pwview//
|
||||
EOT
|
||||
|
||||
# Notify Sentry of this new deploy.
|
||||
# See https://sentry.io/blender-institute/blender-cloud/settings/release-tracking/
|
||||
# and https://docs.sentry.io/api/releases/post-organization-releases/
|
||||
# and https://sentry.io/api/
|
||||
echo
|
||||
echo "==================================================================="
|
||||
REVISION=$(date +'%Y%m%d.%H%M%S.%Z')
|
||||
echo "Notifying Sentry of this new deploy of revision $REVISION."
|
||||
SENTRY_RELEASE_URL="$($SSH env PYTHONPATH="$REMOTE_SECRET_CONFIG_DIR" python3 -c "\"import config_secrets; print(config_secrets.SENTRY_RELEASE_URL)\"")"
|
||||
curl -s "$SENTRY_RELEASE_URL" -XPOST -H 'Content-Type: application/json' -d "{\"version\": \"$REVISION\"}" | json_pp
|
||||
echo
|
||||
|
||||
echo
|
||||
echo "==================================================================="
|
||||
echo "Deploy to $DEPLOYHOST done."
|
||||
echo "==================================================================="
|
1
deploy/build-all.sh
Symbolic link
@@ -0,0 +1 @@
|
||||
build-quick.sh
|
34
deploy/build-quick.sh
Executable file
@@ -0,0 +1,34 @@
|
||||
#!/bin/bash -e
|
||||
|
||||
# macOS does not support readlink -f, so we use greadlink instead
|
||||
if [[ `uname` == 'Darwin' ]]; then
|
||||
command -v greadlink 2>/dev/null 2>&1 || { echo >&2 "Install greadlink using brew."; exit 1; }
|
||||
readlink='greadlink'
|
||||
else
|
||||
readlink='readlink'
|
||||
fi
|
||||
ROOT="$(dirname "$(dirname "$($readlink -f "$0")")")"
|
||||
DOCKERDIR="$ROOT/docker/4_run"
|
||||
|
||||
case "$(basename "$0")" in
|
||||
build-quick.sh)
|
||||
pushd "$ROOT/docker/4_run"
|
||||
./build.sh
|
||||
;;
|
||||
build-all.sh)
|
||||
pushd "$ROOT/docker"
|
||||
./full_rebuild.sh
|
||||
;;
|
||||
*)
|
||||
echo "Unknown script $0, aborting" >&2
|
||||
exit 1
|
||||
esac
|
||||
|
||||
popd
|
||||
echo
|
||||
echo "Press [ENTER] to push the new Docker image."
|
||||
read dummy
|
||||
docker push armadillica/blender_cloud:latest
|
||||
echo
|
||||
echo "Build is done, ready to update the server."
|
||||
|
@@ -1,4 +1,4 @@
|
||||
FROM ubuntu:16.10
|
||||
FROM ubuntu:17.10
|
||||
MAINTAINER Francesco Siddi <francesco@blender.org>
|
||||
|
||||
RUN apt-get update && apt-get install -qyy \
|
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Uses --no-cache to always get the latest upstream (security) upgrades.
|
||||
exec docker build --no-cache "$@" -t pillar_base -f base.docker .
|
||||
exec docker build --no-cache "$@" -t pillar_base .
|
||||
|
@@ -9,7 +9,7 @@ RUN sed -i 's/^# deb-src/deb-src/' /etc/apt/sources.list && \
|
||||
checkinstall \
|
||||
curl
|
||||
|
||||
RUN apt-get build-dep -y python3.5
|
||||
RUN apt-get build-dep -y python3.6
|
||||
|
||||
ADD Python-3.6.4.tar.xz.md5 /Python-3.6.4.tar.xz.md5
|
||||
|
||||
|
@@ -22,7 +22,7 @@ fi
|
||||
echo "Wheelhouse is $WHEELHOUSE"
|
||||
mkdir -p "$WHEELHOUSE"
|
||||
|
||||
docker build -t pillar_wheelbuilder -f build.docker .
|
||||
docker build -t pillar_wheelbuilder .
|
||||
|
||||
GID=$(id -g)
|
||||
docker run --rm -i \
|
||||
|
@@ -27,7 +27,6 @@ RUN mkdir -p $APACHE_RUN_DIR $APACHE_LOCK_DIR $APACHE_LOG_DIR
|
||||
ADD wheelhouse /data/wheelhouse
|
||||
RUN pip3 install --no-index --find-links=/data/wheelhouse -r /data/wheelhouse/requirements.txt
|
||||
|
||||
VOLUME /data/git
|
||||
VOLUME /data/config
|
||||
VOLUME /data/storage
|
||||
VOLUME /var/log
|
||||
@@ -37,12 +36,12 @@ ENV USE_X_SENDFILE True
|
||||
EXPOSE 80
|
||||
EXPOSE 5000
|
||||
|
||||
ADD wsgi-py36.* /etc/apache2/mods-available/
|
||||
ADD apache/wsgi-py36.* /etc/apache2/mods-available/
|
||||
RUN a2enmod rewrite && a2enmod wsgi-py36
|
||||
|
||||
ADD apache2.conf /etc/apache2/apache2.conf
|
||||
ADD 000-default.conf /etc/apache2/sites-available/000-default.conf
|
||||
ADD apache-logrotate.conf /etc/logrotate.d/apache2
|
||||
ADD apache/apache2.conf /etc/apache2/apache2.conf
|
||||
ADD apache/000-default.conf /etc/apache2/sites-available/000-default.conf
|
||||
ADD apache/logrotate.conf /etc/logrotate.d/apache2
|
||||
ADD *.sh /
|
||||
|
||||
# Remove some empty top-level directories we won't use anyway.
|
||||
@@ -53,3 +52,11 @@ RUN rmdir /media /home 2>/dev/null || true
|
||||
ADD bash_history /root/.bash_history
|
||||
|
||||
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
|
||||
RUN python3 -c "import re, secrets; \
|
||||
f = open('/data/git/blender-cloud/config_local.py', 'a'); \
|
||||
h = re.sub(r'[_.~-]', '', secrets.token_urlsafe())[:8]; \
|
||||
print(f'STATIC_FILE_HASH = {h!r}', file=f)"
|
@@ -1,11 +1,12 @@
|
||||
<VirtualHost *:80>
|
||||
XSendFile on
|
||||
XSendFilePath /data/storage/pillar
|
||||
XSendFilePath /data/git/pillar
|
||||
XSendFilePath /data/git/pillar/pillar/web/static/
|
||||
XSendFilePath /data/git/attract/attract/static/
|
||||
XSendFilePath /data/git/flamenco/flamenco/static/
|
||||
XsendFilePath /data/git/pillar-svnman/svnman/static/
|
||||
XsendFilePath /data/git/blender-cloud
|
||||
XsendFilePath /data/git/blender-cloud/static/
|
||||
XsendFilePath /data/git/blender-cloud/cloud/static/
|
||||
|
||||
ServerAdmin webmaster@localhost
|
||||
DocumentRoot /var/www/html
|
||||
@@ -19,7 +20,7 @@
|
||||
ErrorLog ${APACHE_LOG_DIR}/error.log
|
||||
CustomLog ${APACHE_LOG_DIR}/access.log combined
|
||||
|
||||
WSGIDaemonProcess cloud processes=2 threads=32 maximum-requests=10000
|
||||
WSGIDaemonProcess cloud processes=2 threads=64 maximum-requests=10000
|
||||
WSGIPassAuthorization On
|
||||
|
||||
WSGIScriptAlias / /data/git/blender-cloud/runserver.wsgi \
|
||||
@@ -39,7 +40,7 @@
|
||||
# Redirects for blender-cloud projects
|
||||
RewriteRule "^/p/blender-cloud/?$" "/blog" [R=301,L]
|
||||
RewriteRule "^/agent327/?$" "/p/agent-327" [R=301,L]
|
||||
RewriteRule "^/caminandes/?$" "/p/caminandes" [R=301,L]
|
||||
RewriteRule "^/caminandes/?$" "/p/caminandes-3" [R=301,L]
|
||||
RewriteRule "^/cf2/?$" "/p/creature-factory-2" [R=301,L]
|
||||
RewriteRule "^/characters/?$" "/p/characters" [R=301,L]
|
||||
RewriteRule "^/gallery/?$" "/p/gallery" [R=301,L]
|
||||
@@ -47,5 +48,7 @@
|
||||
RewriteRule "^/textures/?$" "/p/textures" [R=301,L]
|
||||
RewriteRule "^/training/?$" "/courses" [R=301,L]
|
||||
RewriteRule "^/spring/?$" "/p/spring" [R=301,L]
|
||||
|
||||
RewriteRule "^/hero/?$" "/p/hero" [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>
|
@@ -1,5 +1,5 @@
|
||||
#!/bin/bash -e
|
||||
|
||||
docker build -t armadillica/blender_cloud:latest -f run.docker .
|
||||
docker build -t armadillica/blender_cloud:latest .
|
||||
|
||||
echo "Done, built armadillica/blender_cloud:latest"
|
||||
|
131
docker/4_run/config_local.py
Normal file
@@ -0,0 +1,131 @@
|
||||
import os
|
||||
from collections import defaultdict
|
||||
|
||||
DEBUG = False
|
||||
|
||||
SCHEME = 'https'
|
||||
PREFERRED_URL_SCHEME = 'https'
|
||||
SERVER_NAME = 'cloud.blender.org'
|
||||
|
||||
# os.environ['OAUTHLIB_INSECURE_TRANSPORT'] = 'true'
|
||||
os.environ['PILLAR_MONGO_DBNAME'] = 'cloud'
|
||||
os.environ['PILLAR_MONGO_PORT'] = '27017'
|
||||
os.environ['PILLAR_MONGO_HOST'] = 'mongo'
|
||||
|
||||
USE_X_SENDFILE = True
|
||||
|
||||
STORAGE_BACKEND = 'gcs'
|
||||
|
||||
CDN_SERVICE_DOMAIN = 'blendercloud-pro.r.worldssl.net'
|
||||
CDN_CONTENT_SUBFOLDER = ''
|
||||
CDN_STORAGE_ADDRESS = 'push-11.cdnsun.com'
|
||||
|
||||
CACHE_TYPE = 'redis' # null
|
||||
CACHE_KEY_PREFIX = 'pw_'
|
||||
CACHE_REDIS_HOST = 'redis'
|
||||
CACHE_REDIS_PORT = '6379'
|
||||
CACHE_REDIS_URL = 'redis://redis:6379'
|
||||
|
||||
PILLAR_SERVER_ENDPOINT = 'https://cloud.blender.org/api/'
|
||||
|
||||
BLENDER_ID_ENDPOINT = 'https://www.blender.org/id'
|
||||
|
||||
GCLOUD_APP_CREDENTIALS = '/data/config/google_app.json'
|
||||
GCLOUD_PROJECT = 'blender-cloud'
|
||||
|
||||
MAIN_PROJECT_ID = '563a9c8cf0e722006ce97b03'
|
||||
# MAIN_PROJECT_ID = '57aa07c088bef606e89078bd'
|
||||
|
||||
ALGOLIA_INDEX_USERS = 'pro_Users'
|
||||
ALGOLIA_INDEX_NODES = 'pro_Nodes'
|
||||
|
||||
ZENCODER_NOTIFICATIONS_URL = 'https://cloud.blender.org/api/encoding/zencoder/notifications'
|
||||
|
||||
FILE_LINK_VALIDITY = defaultdict(
|
||||
lambda: 3600 * 24 * 30, # default of 1 month.
|
||||
gcs=3600 * 23, # 23 hours for Google Cloud Storage.
|
||||
cdnsun=3600 * 23
|
||||
)
|
||||
|
||||
LOGGING = {
|
||||
'version': 1,
|
||||
'formatters': {
|
||||
'default': {'format': '%(levelname)8s %(name)s %(message)s'}
|
||||
},
|
||||
'handlers': {
|
||||
'console': {
|
||||
'class': 'logging.StreamHandler',
|
||||
'formatter': 'default',
|
||||
'stream': 'ext://sys.stderr',
|
||||
}
|
||||
},
|
||||
'loggers': {
|
||||
'pillar': {'level': 'INFO'},
|
||||
# 'pillar.auth': {'level': 'DEBUG'},
|
||||
# 'pillar.api.blender_id': {'level': 'DEBUG'},
|
||||
# 'pillar.api.blender_cloud.subscription': {'level': 'DEBUG'},
|
||||
'bcloud': {'level': 'INFO'},
|
||||
'cloud': {'level': 'INFO'},
|
||||
'attract': {'level': 'INFO'},
|
||||
'flamenco': {'level': 'INFO'},
|
||||
# 'pillar.api.file_storage': {'level': 'DEBUG'},
|
||||
# 'pillar.api.file_storage.ensure_valid_link': {'level': 'INFO'},
|
||||
'pillar.api.file_storage.refresh_links_for_backend': {'level': 'DEBUG'},
|
||||
'werkzeug': {'level': 'DEBUG'},
|
||||
'eve': {'level': 'WARNING'},
|
||||
# 'elasticsearch': {'level': 'DEBUG'},
|
||||
},
|
||||
'root': {
|
||||
'level': 'WARNING',
|
||||
'handlers': [
|
||||
'console',
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
REDIRECTS = {
|
||||
# For old links, refer to the services page (hopefully it refreshes then)
|
||||
'downloads/blender_cloud-latest-bundle.zip': 'https://cloud.blender.org/services#blender-addon',
|
||||
|
||||
# Latest Blender Cloud add-on; remember to update BLENDER_CLOUD_ADDON_VERSION.
|
||||
'downloads/blender_cloud-latest-addon.zip':
|
||||
'https://storage.googleapis.com/institute-storage/addons/blender_cloud-1.8.0.addon.zip',
|
||||
|
||||
# Redirect old Grafista endpoint to /stats
|
||||
'/stats/': '/stats',
|
||||
}
|
||||
|
||||
# Latest version of the add-on; remember to update REDIRECTS.
|
||||
BLENDER_CLOUD_ADDON_VERSION = '1.8.0'
|
||||
|
||||
UTM_LINKS = {
|
||||
'cartoon_brew': {
|
||||
'image': 'https://imgur.com/13nQTi3.png',
|
||||
'link': 'https://store.blender.org/product/membership/'
|
||||
}
|
||||
}
|
||||
|
||||
# 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/'
|
||||
|
||||
# Mail options, see pillar.celery.email_tasks.
|
||||
SMTP_HOST = 'proog.blender.org'
|
||||
SMTP_PORT = 25
|
||||
SMTP_USERNAME = 'server@blender.cloud'
|
||||
SMTP_TIMEOUT = 30 # timeout in seconds, https://docs.python.org/3/library/smtplib.html#smtplib.SMTP
|
||||
MAIL_RETRY = 180 # in seconds, delay until trying to send an email again.
|
||||
MAIL_DEFAULT_FROM_NAME = 'Blender Cloud'
|
||||
MAIL_DEFAULT_FROM_ADDR = 'cloudsupport@blender.org'
|
||||
|
||||
# MUST be 8 characters long, see pillar.flask_extra.HashedPathConverter
|
||||
# STATIC_FILE_HASH = '12345678'
|
||||
# The value used in production is appended from Dockerfile.
|
@@ -1,18 +1,20 @@
|
||||
|
||||
if [ ! -f /installed ]; then
|
||||
SITEPKG=$(echo /opt/python/lib/python3.*/site-packages)
|
||||
echo "Installing Blender Cloud packages into $SITEPKG"
|
||||
if [ -f /installed ]; then
|
||||
return
|
||||
fi
|
||||
|
||||
# TODO: 'pip3 install -e' runs 'setup.py develop', which runs 'setup.py egg_info',
|
||||
# which can't write the egg info to the read-only /data/git volume. This is why
|
||||
# we manually install the links.
|
||||
for SUBPROJ in /data/git/{pillar,pillar-python-sdk,attract,flamenco,pillar-svnman}; do
|
||||
SITEPKG=$(echo /opt/python/lib/python3.*/site-packages)
|
||||
echo "Installing Blender Cloud packages into $SITEPKG"
|
||||
|
||||
# TODO: 'pip3 install -e' runs 'setup.py develop', which runs 'setup.py egg_info',
|
||||
# which can't write the egg info to the read-only /data/git volume. This is why
|
||||
# we manually install the links.
|
||||
for SUBPROJ in /data/git/{pillar,pillar-python-sdk,attract,flamenco,pillar-svnman}; do
|
||||
NAME=$(python3 $SUBPROJ/setup.py --name)
|
||||
echo "... $NAME"
|
||||
echo $SUBPROJ >> $SITEPKG/easy-install.pth
|
||||
echo $SUBPROJ > $SITEPKG/$NAME.egg-link
|
||||
done
|
||||
echo "All packages installed."
|
||||
done
|
||||
echo "All packages installed."
|
||||
|
||||
touch /installed
|
||||
fi
|
||||
touch /installed
|
||||
|
@@ -58,6 +58,7 @@ Place TLS certificates in `/data/certs/{cloud,cloudapi}.blender.org.pem`.
|
||||
They should contain (in order) the private key, the host certificate, and the
|
||||
CA certificate.
|
||||
|
||||
|
||||
## 6. Create a local config
|
||||
|
||||
Blender Cloud expects the following files to exist:
|
||||
@@ -65,6 +66,9 @@ Blender Cloud expects the following files to exist:
|
||||
- `/data/git/blender_cloud/config_local.py` with machine-local configuration overrides
|
||||
- `/data/config/google_app.json` with Google Cloud Storage credentials.
|
||||
|
||||
When run from Docker, the `docker/4_run/config_local.py` file will be used. Overrides for that file
|
||||
can be placed in `/data/config/config_secrets.py`.
|
||||
|
||||
|
||||
## 7. ElasticSearch & kibana
|
||||
|
||||
|
@@ -9,6 +9,11 @@ services:
|
||||
- /data/storage/db-bak:/data/db-bak # for backing up stuff etc.
|
||||
ports:
|
||||
- "127.0.0.1:27017:27017"
|
||||
logging:
|
||||
driver: "json-file"
|
||||
options:
|
||||
max-size: "200k"
|
||||
max-file: "20"
|
||||
|
||||
redis:
|
||||
image: redis:3.2.8
|
||||
@@ -16,6 +21,11 @@ services:
|
||||
restart: always
|
||||
ports:
|
||||
- "127.0.0.1:6379:6379"
|
||||
logging:
|
||||
driver: "json-file"
|
||||
options:
|
||||
max-size: "200k"
|
||||
max-file: "20"
|
||||
|
||||
rabbit:
|
||||
image: rabbitmq:3.6.10
|
||||
@@ -23,6 +33,11 @@ services:
|
||||
restart: always
|
||||
ports:
|
||||
- "127.0.0.1:5672:5672"
|
||||
logging:
|
||||
driver: "json-file"
|
||||
options:
|
||||
max-size: "200k"
|
||||
max-file: "20"
|
||||
|
||||
elastic:
|
||||
image: armadillica/elasticsearch:6.1.1
|
||||
@@ -35,6 +50,11 @@ services:
|
||||
- "127.0.0.1:9200:9200"
|
||||
environment:
|
||||
ES_JAVA_OPTS: "-Xms256m -Xmx256m"
|
||||
logging:
|
||||
driver: "json-file"
|
||||
options:
|
||||
max-size: "200k"
|
||||
max-file: "20"
|
||||
|
||||
elasticproxy:
|
||||
image: armadillica/elasticproxy:1.2
|
||||
@@ -43,6 +63,11 @@ services:
|
||||
command: /elasticproxy -elastic http://elastic:9200/
|
||||
depends_on:
|
||||
- elastic
|
||||
logging:
|
||||
driver: "json-file"
|
||||
options:
|
||||
max-size: "200k"
|
||||
max-file: "20"
|
||||
|
||||
kibana:
|
||||
image: armadillica/kibana:6.1.1
|
||||
@@ -52,7 +77,7 @@ services:
|
||||
SERVER_NAME: "stats.cloud.blender.org"
|
||||
ELASTICSEARCH_URL: http://elasticproxy:9200
|
||||
CONSOLE_ENABLED: 'false'
|
||||
VIRTUAL_HOST: http://stats.cloud.blender.org/*,https://stats.cloud.blender.org/*,http://stats.blender-cloud/*,https://stats.blender-cloud/*
|
||||
VIRTUAL_HOST: http://stats.cloud.blender.org/*,https://stats.cloud.blender.org/*,http://stats.cloud.local/*,https://stats.cloud.local/*
|
||||
VIRTUAL_HOST_WEIGHT: 20
|
||||
FORCE_SSL: "true"
|
||||
|
||||
@@ -60,19 +85,24 @@ services:
|
||||
NODE_OPTIONS: "--max-old-space-size=200"
|
||||
depends_on:
|
||||
- elasticproxy
|
||||
logging:
|
||||
driver: "json-file"
|
||||
options:
|
||||
max-size: "200k"
|
||||
max-file: "20"
|
||||
|
||||
blender_cloud:
|
||||
image: armadillica/blender_cloud:latest
|
||||
container_name: blender_cloud
|
||||
restart: always
|
||||
environment:
|
||||
VIRTUAL_HOST: http://cloud.blender.org/*,https://cloud.blender.org/*,http://blender-cloud/*,https://blender-cloud/*
|
||||
VIRTUAL_HOST: http://cloud.blender.org/*,https://cloud.blender.org/*,http://cloud.local/*,https://cloud.local/*
|
||||
VIRTUAL_HOST_WEIGHT: 10
|
||||
FORCE_SSL: "true"
|
||||
GZIP_COMPRESSION_TYPE: "text/html text/plain text/css application/javascript"
|
||||
PILLAR_CONFIG: /data/config/config_secrets.py
|
||||
volumes:
|
||||
# format: HOST:CONTAINER
|
||||
- /data/git:/data/git:ro
|
||||
- /data/config:/data/config:ro
|
||||
- /data/storage/pillar:/data/storage/pillar
|
||||
- /data/log:/var/log
|
||||
@@ -86,9 +116,10 @@ services:
|
||||
entrypoint: /celery-worker.sh
|
||||
container_name: celery_worker
|
||||
restart: always
|
||||
environment:
|
||||
PILLAR_CONFIG: /data/config/config_secrets.py
|
||||
volumes:
|
||||
# format: HOST:CONTAINER
|
||||
- /data/git:/data/git:ro
|
||||
- /data/config:/data/config:ro
|
||||
- /data/storage/pillar:/data/storage/pillar
|
||||
- /data/log:/var/log
|
||||
@@ -96,44 +127,44 @@ services:
|
||||
- mongo
|
||||
- redis
|
||||
- rabbit
|
||||
|
||||
celery_beat:
|
||||
image: armadillica/blender_cloud:latest
|
||||
entrypoint: /celery-beat.sh
|
||||
container_name: celery_beat
|
||||
restart: always
|
||||
volumes:
|
||||
# format: HOST:CONTAINER
|
||||
- /data/git:/data/git:ro
|
||||
- /data/storage/pillar:/data/storage/pillar
|
||||
- /data/log:/var/log
|
||||
depends_on:
|
||||
- mongo
|
||||
- redis
|
||||
- rabbit
|
||||
logging:
|
||||
driver: "json-file"
|
||||
options:
|
||||
max-size: "200k"
|
||||
max-file: "20"
|
||||
|
||||
grafista:
|
||||
image: armadillica/grafista:latest
|
||||
container_name: grafista
|
||||
celery_beat:
|
||||
image: armadillica/blender_cloud:latest
|
||||
entrypoint: /celery-beat.sh
|
||||
container_name: celery_beat
|
||||
restart: always
|
||||
environment:
|
||||
PILLAR_CONFIG: /data/config/config_secrets.py
|
||||
volumes:
|
||||
- /data/git/grafista:/data/git/grafista:ro
|
||||
- /data/storage/grafista:/data/storage/grafista
|
||||
# format: HOST:CONTAINER
|
||||
- /data/config:/data/config:ro
|
||||
- /data/storage/pillar:/data/storage/pillar
|
||||
- /data/log:/var/log
|
||||
depends_on:
|
||||
- mongo
|
||||
- redis
|
||||
- rabbit
|
||||
- celery_worker
|
||||
logging:
|
||||
driver: "json-file"
|
||||
options:
|
||||
max-size: "200k"
|
||||
max-file: "20"
|
||||
|
||||
letsencrypt:
|
||||
image: armadillica/picohttp:latest
|
||||
image: armadillica/picohttp:1.0
|
||||
container_name: letsencrypt
|
||||
restart: always
|
||||
environment:
|
||||
WEBROOT: /data/letsencrypt
|
||||
LISTEN: '[::]:80'
|
||||
VIRTUAL_HOST: http://cloud.blender.org/.well-known/*, http://stats.cloud.blender.org/.well-known/*
|
||||
VIRTUAL_HOST_WEIGHT: 20
|
||||
VIRTUAL_HOST_WEIGHT: 30
|
||||
volumes:
|
||||
- /data/letsencrypt:/data/letsencrypt
|
||||
|
||||
|
5
docker/mongo-backup.cron
Normal file
@@ -0,0 +1,5 @@
|
||||
# Change to suit your needs, then place in /etc/cron.d/mongo-backup
|
||||
# (so remove the .cron from the name)
|
||||
|
||||
MAILTO=yourname@youraddress.org
|
||||
30 5 * * * root /root/docker/mongo-backup.sh
|
28
docker/mongo-backup.sh
Executable file
@@ -0,0 +1,28 @@
|
||||
#!/bin/bash -e
|
||||
|
||||
BACKUPDIR=/data/storage/db-bak
|
||||
DATE=$(date +'%Y-%m-%d-%H%M')
|
||||
ARCHIVE=$BACKUPDIR/mongo-live-$DATE.tar.xz
|
||||
|
||||
# Just a sanity check before we give it to 'rm -rf'
|
||||
if [ -z "$DATE" ]; then
|
||||
echo "Empty string found where the date should be, aborting."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
# /data/db-bak in Docker is /data/storage/db-bak on the host.
|
||||
docker exec mongo mongodump -d cloud \
|
||||
--out /data/db-bak/dump-$DATE \
|
||||
--excludeCollection tokens \
|
||||
--excludeCollection flamenco_task_logs \
|
||||
--quiet
|
||||
|
||||
cd $BACKUPDIR
|
||||
tar -Jcf $ARCHIVE dump-$DATE/
|
||||
rm -rf dump-$DATE
|
||||
|
||||
TO_DELETE="$(ls $BACKUPDIR/mongo-live-*.tar.xz | head -n -7)"
|
||||
[ -z "$TO_DELETE" ] || rm "$TO_DELETE"
|
||||
|
||||
rsync -a $BACKUPDIR/mongo-live-*.tar.xz cloud-backup@swami-direct.blender.cloud:
|
12
gulp
@@ -14,6 +14,18 @@ function install() {
|
||||
if [ "$1" == "watch" ]; then
|
||||
# Treat "gulp watch" as "gulp && gulp watch"
|
||||
$GULP
|
||||
elif [ "$1" == "all" ]; then
|
||||
# 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[@]}"
|
||||
do
|
||||
cd ../$r
|
||||
./gulp
|
||||
done
|
||||
exit 1
|
||||
fi
|
||||
|
||||
exec $GULP "$@"
|
||||
|
42
gulpfile.js
@@ -1,5 +1,6 @@
|
||||
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');
|
||||
@@ -11,8 +12,7 @@ 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');
|
||||
var cache = require('gulp-cached');
|
||||
var uglify = require('gulp-uglify-es').default;
|
||||
|
||||
var enabled = {
|
||||
uglify: argv.production,
|
||||
@@ -21,6 +21,7 @@ var enabled = {
|
||||
prettyPug: !argv.production,
|
||||
cachify: !argv.production,
|
||||
cleanup: argv.production,
|
||||
chmod: argv.production,
|
||||
};
|
||||
|
||||
var destination = {
|
||||
@@ -29,6 +30,12 @@ var destination = {
|
||||
js: 'cloud/static/assets/js',
|
||||
}
|
||||
|
||||
var source = {
|
||||
pillar: '../pillar/',
|
||||
bootstrap: 'node_modules/bootstrap/',
|
||||
popper: 'node_modules/popper.js/'
|
||||
}
|
||||
|
||||
|
||||
/* CSS */
|
||||
gulp.task('styles', function() {
|
||||
@@ -73,7 +80,7 @@ gulp.task('scripts', function() {
|
||||
.pipe(gulpif(enabled.uglify, uglify()))
|
||||
.pipe(rename({suffix: '.min'}))
|
||||
.pipe(gulpif(enabled.maps, sourcemaps.write(".")))
|
||||
.pipe(chmod(644))
|
||||
.pipe(gulpif(enabled.chmod, chmod(644)))
|
||||
.pipe(gulp.dest(destination.js))
|
||||
.pipe(gulpif(argv.livereload, livereload()));
|
||||
});
|
||||
@@ -88,12 +95,34 @@ gulp.task('scripts_concat_tutti', function() {
|
||||
.pipe(concat("tutti.min.js"))
|
||||
.pipe(gulpif(enabled.uglify, uglify()))
|
||||
.pipe(gulpif(enabled.maps, sourcemaps.write(".")))
|
||||
.pipe(chmod(644))
|
||||
.pipe(gulpif(enabled.chmod, chmod(644)))
|
||||
.pipe(gulp.dest(destination.js))
|
||||
.pipe(gulpif(argv.livereload, livereload()));
|
||||
});
|
||||
|
||||
|
||||
// Combine all needed Bootstrap JavaScript into a single file.
|
||||
gulp.task('scripts_concat_bootstrap', function() {
|
||||
|
||||
toUglify = [
|
||||
source.popper + 'dist/umd/popper.min.js',
|
||||
source.bootstrap + 'js/dist/index.js',
|
||||
source.bootstrap + 'js/dist/util.js',
|
||||
source.bootstrap + 'js/dist/tooltip.js',
|
||||
source.bootstrap + 'js/dist/dropdown.js',
|
||||
];
|
||||
|
||||
gulp.src(toUglify)
|
||||
.pipe(gulpif(enabled.failCheck, plumber()))
|
||||
.pipe(gulpif(enabled.maps, sourcemaps.init()))
|
||||
.pipe(concat("bootstrap.min.js"))
|
||||
.pipe(gulpif(enabled.uglify, uglify()))
|
||||
.pipe(gulpif(enabled.maps, sourcemaps.write(".")))
|
||||
.pipe(gulpif(enabled.chmod, chmod(644)))
|
||||
.pipe(gulp.dest(destination.js))
|
||||
.pipe(gulpif(argv.livereload, livereload()));
|
||||
});
|
||||
|
||||
|
||||
// While developing, run 'gulp watch'
|
||||
gulp.task('watch',function() {
|
||||
@@ -103,6 +132,8 @@ gulp.task('watch',function() {
|
||||
}
|
||||
|
||||
gulp.watch('src/styles/**/*.sass',['styles']);
|
||||
gulp.watch(source.pillar + 'src/styles/**/*.sass',['styles']);
|
||||
|
||||
gulp.watch('src/templates/**/*.pug',['templates']);
|
||||
gulp.watch('src/scripts/*.js',['scripts']);
|
||||
gulp.watch('src/scripts/tutti/**/*.js',['scripts_concat_tutti']);
|
||||
@@ -125,4 +156,5 @@ gulp.task('cleanup', function() {
|
||||
// Run 'gulp' to build everything at once
|
||||
var tasks = [];
|
||||
if (enabled.cleanup) tasks.push('cleanup');
|
||||
gulp.task('default', tasks.concat(['styles', 'templates', 'scripts', 'scripts_concat_tutti']));
|
||||
|
||||
gulp.task('default', tasks.concat(['styles', 'templates', 'scripts', 'scripts_concat_tutti', 'scripts_concat_bootstrap']));
|
||||
|
5601
package-lock.json
generated
Normal file
31
package.json
@@ -8,19 +8,24 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"gulp": "~3.9.1",
|
||||
"gulp-autoprefixer": "~2.3.1",
|
||||
"gulp-cached": "~1.1.0",
|
||||
"gulp-chmod": "~1.3.0",
|
||||
"gulp-concat": "~2.6.0",
|
||||
"gulp-if": "^2.0.1",
|
||||
"gulp-git": "~2.4.2",
|
||||
"gulp-pug": "~3.2.0",
|
||||
"gulp-livereload": "~3.8.1",
|
||||
"gulp-plumber": "~1.1.0",
|
||||
"gulp-rename": "~1.2.2",
|
||||
"gulp-sass": "~2.3.1",
|
||||
"gulp-sourcemaps": "~1.6.0",
|
||||
"gulp-uglify": "~1.5.3",
|
||||
"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-sourcemaps": "~2.6.4",
|
||||
"gulp-uglify-es": "^1.0.4",
|
||||
"minimist": "^1.2.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"bootstrap": "^4.1.3",
|
||||
"jquery": "^3.3.1",
|
||||
"popper.js": "^1.14.4"
|
||||
}
|
||||
}
|
||||
|
91
rsync_ui.sh
@@ -1,91 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -e # error out when one of the commands in the script errors.
|
||||
|
||||
if [ -z "$1" ]; then
|
||||
echo "Usage: $0 {host-to-deploy-to}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
DEPLOYHOST="$1"
|
||||
|
||||
# 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
|
||||
|
||||
BLENDER_CLOUD_DIR="$(dirname "$($readlink -f "$0")")"
|
||||
if [ ! -d "$BLENDER_CLOUD_DIR" ]; then
|
||||
echo "Unable to find Blender Cloud dir '$BLENDER_CLOUD_DIR'"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
BLENDER_CLOUD_ASSETS="$BLENDER_CLOUD_DIR/cloud/static/"
|
||||
BLENDER_CLOUD_TEMPLATES="$BLENDER_CLOUD_DIR/cloud/templates/"
|
||||
|
||||
if [ ! -d "$BLENDER_CLOUD_ASSETS" ]; then
|
||||
echo "Unable to find assets dir $BLENDER_CLOUD_ASSETS"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cd $BLENDER_CLOUD_DIR
|
||||
if [ $(git rev-parse --abbrev-ref HEAD) != "production" ]; then
|
||||
echo "You are NOT on the production branch, refusing to rsync_ui." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
PILLAR_DIR=$(python <<EOT
|
||||
from __future__ import print_function
|
||||
import os.path
|
||||
import pillar
|
||||
|
||||
print(os.path.dirname(os.path.dirname(pillar.__file__)))
|
||||
EOT
|
||||
)
|
||||
|
||||
PILLAR_ASSETS="$PILLAR_DIR/pillar/web/static/assets/"
|
||||
PILLAR_TEMPLATES="$PILLAR_DIR/pillar/web/templates/"
|
||||
|
||||
if [ ! -d "$PILLAR_ASSETS" ]; then
|
||||
echo "Unable to find assets dir $PILLAR_ASSETS"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cd $PILLAR_DIR
|
||||
if [ $(git rev-parse --abbrev-ref HEAD) != "production" ]; then
|
||||
echo "Pillar is NOT on the production branch, refusing to rsync_ui." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo
|
||||
echo "*** GULPA GULPA PILLAR ***"
|
||||
# TODO(Pablo): this command fails when passing the --production CLI
|
||||
# arg.
|
||||
./gulp
|
||||
|
||||
echo
|
||||
echo "*** SYNCING PILLAR_ASSETS ***"
|
||||
rsync -avh $PILLAR_ASSETS root@${DEPLOYHOST}:/data/git/pillar/pillar/web/static/assets/ --delete-after
|
||||
|
||||
echo
|
||||
echo "*** SYNCING PILLAR_TEMPLATES ***"
|
||||
rsync -avh $PILLAR_TEMPLATES root@${DEPLOYHOST}:/data/git/pillar/pillar/web/templates/ --delete-after
|
||||
|
||||
|
||||
cd $BLENDER_CLOUD_DIR
|
||||
|
||||
echo
|
||||
echo "*** GULPA GULPA BLENDER_CLOUD ***"
|
||||
./gulp --production
|
||||
|
||||
echo
|
||||
echo "*** SYNCING BLENDER_CLOUD_ASSETS ***"
|
||||
# Exclude files managed by Git.
|
||||
rsync -avh $BLENDER_CLOUD_ASSETS --exclude js/vendor/ root@${DEPLOYHOST}:/data/git/blender-cloud/cloud/static/ --delete-after
|
||||
|
||||
echo
|
||||
echo "*** SYNCING BLENDER_CLOUD_TEMPLATES ***"
|
||||
rsync -avh $BLENDER_CLOUD_TEMPLATES root@${DEPLOYHOST}:/data/git/blender-cloud/cloud/templates/ --delete-after
|
9
src/styles/_about.sass
Normal file
@@ -0,0 +1,9 @@
|
||||
section.team
|
||||
h2, .people-container
|
||||
text-align: center
|
||||
h3
|
||||
margin-bottom: 0
|
||||
h3 small
|
||||
display: block
|
||||
.people-intro, .row
|
||||
margin-bottom: 20px
|
@@ -1,61 +1,22 @@
|
||||
.dashboard-container
|
||||
+container-behavior
|
||||
+media-xs
|
||||
flex-direction: column
|
||||
align-content: center
|
||||
align-items: flex-start
|
||||
display: flex
|
||||
justify-content: space-around
|
||||
word-break: break-word
|
||||
|
||||
section.dashboard-main,
|
||||
section.dashboard-secondary
|
||||
+media-xs
|
||||
width: 100%
|
||||
margin: 20px auto
|
||||
|
||||
img
|
||||
max-width: 100%
|
||||
|
||||
section.dashboard-main
|
||||
+container-box
|
||||
width: 52%
|
||||
|
||||
section.dashboard-secondary
|
||||
width: 46%
|
||||
flex-direction: column
|
||||
margin-right: auto
|
||||
|
||||
span.section-lead
|
||||
display: block
|
||||
padding: 10px 0
|
||||
color: $color-text-dark-secondary
|
||||
|
||||
section.dashboard-main,
|
||||
section.dashboard-secondary
|
||||
h4
|
||||
.title-underline
|
||||
padding-bottom: 5px
|
||||
margin-bottom: 20px
|
||||
position: relative
|
||||
margin-bottom: 20px
|
||||
|
||||
&:before
|
||||
position: absolute
|
||||
width: 50px
|
||||
height: 2px
|
||||
top: 125%
|
||||
background-color: $primary
|
||||
content: ' '
|
||||
display: block
|
||||
background-color: $color-primary
|
||||
height: 2px
|
||||
top: 125%
|
||||
position: absolute
|
||||
width: 50px
|
||||
|
||||
a
|
||||
color: $color-text
|
||||
|
||||
&:hover
|
||||
color: $color-primary
|
||||
cursor: pointer
|
||||
|
||||
nav#nav-tabs,
|
||||
nav#sub-nav-tabs
|
||||
nav#nav-tabs,
|
||||
nav#sub-nav-tabs
|
||||
ul#nav-tabs__list,
|
||||
ul#sub-nav-tabs__list
|
||||
margin: 0
|
||||
@@ -113,37 +74,13 @@
|
||||
border-color: $color-background-light
|
||||
pointer-events: none
|
||||
|
||||
li.create
|
||||
cursor: pointer
|
||||
display: inline-block
|
||||
float: right
|
||||
font:
|
||||
size: 1.2em
|
||||
weight: 400
|
||||
padding: 5px 10px
|
||||
margin-top: 3px
|
||||
|
||||
a
|
||||
color: $color-success
|
||||
text-decoration: none
|
||||
|
||||
&.disabled
|
||||
cursor: wait
|
||||
border-color: $color-success
|
||||
opacity: .8
|
||||
a
|
||||
cursor: wait
|
||||
|
||||
section.stream
|
||||
background-color: white
|
||||
border-bottom: thin solid $color-background-dark
|
||||
.dashboard-container
|
||||
word-break: break-word
|
||||
|
||||
section.stream
|
||||
ul.activity-stream__list
|
||||
list-style: none
|
||||
margin: 0
|
||||
padding: 0
|
||||
|
||||
$activity-stream-thumbnail-size: 110px
|
||||
|
||||
> li
|
||||
position: relative
|
||||
display: flex
|
||||
@@ -180,17 +117,6 @@
|
||||
transition: font-size 100ms ease-in-out
|
||||
|
||||
&.comment
|
||||
.activity-stream__list-thumbnail
|
||||
background: transparent
|
||||
color: $node-type-comment
|
||||
font-size: 1.2em
|
||||
box-shadow: none
|
||||
|
||||
i
|
||||
+position-center-translate
|
||||
left: 22px
|
||||
top: 19px
|
||||
|
||||
.activity-stream__list-details
|
||||
padding: 0
|
||||
.title
|
||||
@@ -200,6 +126,8 @@
|
||||
margin: 0
|
||||
|
||||
ul.meta
|
||||
+list-meta
|
||||
font-size: .9em
|
||||
padding: 0 10px 7px 10px
|
||||
|
||||
li
|
||||
@@ -300,7 +228,6 @@
|
||||
overflow: hidden
|
||||
position: relative
|
||||
max-width: 100%
|
||||
margin-right: auto
|
||||
padding: 10px 0
|
||||
|
||||
+media-xs
|
||||
@@ -310,18 +237,14 @@
|
||||
+ribbon
|
||||
right: -47px
|
||||
top: 5px
|
||||
font:
|
||||
size: 12px
|
||||
weight: 500
|
||||
font-size: 12px
|
||||
|
||||
span
|
||||
padding: 1px 50px
|
||||
|
||||
.title
|
||||
display: inline-block
|
||||
padding: 0 10px
|
||||
color: $color-text-dark
|
||||
font-size: 1.1em
|
||||
|
||||
span
|
||||
@include badge(hsl(hue($color-success), 60%, 45%), 3px)
|
||||
@@ -329,25 +252,7 @@
|
||||
padding: 1px 5px
|
||||
margin-right: 5px
|
||||
|
||||
ul.meta
|
||||
+list-meta
|
||||
padding: 5px 10px 0 10px
|
||||
font-size: .85em
|
||||
color: $color-text-dark-secondary
|
||||
display: flex
|
||||
white-space: nowrap
|
||||
|
||||
&.extra
|
||||
margin-top: auto
|
||||
|
||||
li
|
||||
padding-left: 10px
|
||||
&:before
|
||||
left: -5px
|
||||
&.where-project
|
||||
+text-overflow-ellipsis
|
||||
|
||||
section.comments
|
||||
section.comments
|
||||
padding: 0 15px 5px
|
||||
|
||||
ul
|
||||
@@ -371,119 +276,7 @@
|
||||
display: block
|
||||
padding-bottom: 5px
|
||||
|
||||
section.blog-stream
|
||||
+media-md
|
||||
padding-left: 10px
|
||||
+media-sm
|
||||
padding-left: 10px
|
||||
position: relative
|
||||
|
||||
.feed
|
||||
position: absolute
|
||||
top: 10px
|
||||
right: 10px
|
||||
font-size: 1.4em
|
||||
color: lighten($color-text-dark-hint, 10%)
|
||||
|
||||
&:hover
|
||||
color: $color-primary
|
||||
|
||||
> ul
|
||||
margin: 0
|
||||
padding: 0
|
||||
list-style: none
|
||||
border-top: thin solid $color-background
|
||||
|
||||
.blog_index-item
|
||||
+container-box
|
||||
display: flex
|
||||
flex-direction: column
|
||||
margin-bottom: 50px
|
||||
|
||||
&:before
|
||||
height: 1px
|
||||
background-color: $color-background-dark
|
||||
position: absolute
|
||||
bottom: -26px
|
||||
left: 25px
|
||||
right: 25px
|
||||
content: ' '
|
||||
|
||||
&:last-child
|
||||
margin-bottom: 0
|
||||
|
||||
&:before
|
||||
display: none
|
||||
|
||||
video
|
||||
max-width: 100%
|
||||
|
||||
a.item-title
|
||||
font-size: 1.6em
|
||||
padding: 5px 15px
|
||||
display: block
|
||||
color: $color-text
|
||||
|
||||
&:hover
|
||||
color: $color-primary
|
||||
|
||||
ul.meta
|
||||
+list-meta
|
||||
font-size: .9em
|
||||
padding: 15px 15px 5px
|
||||
|
||||
|
||||
&.blog-non-featured
|
||||
border-radius: 0
|
||||
margin: 0
|
||||
|
||||
.item-content
|
||||
+node-details-description
|
||||
padding: 10px 15px
|
||||
|
||||
.blog-stream__list-details
|
||||
.title
|
||||
color: $color-text-dark-primary
|
||||
display: block
|
||||
font-size: 1.3em
|
||||
|
||||
&:hover
|
||||
color: $color-primary
|
||||
|
||||
ul.meta
|
||||
+list-meta
|
||||
padding-top: 5px
|
||||
font-size: .9em
|
||||
color: $color-text-dark-secondary
|
||||
|
||||
li
|
||||
padding-left: 10px
|
||||
&:before
|
||||
left: -5px
|
||||
|
||||
.blog_index-header
|
||||
display: block
|
||||
position: relative
|
||||
|
||||
img
|
||||
border-top-left-radius: 3px
|
||||
border-top-right-radius: 3px
|
||||
width: 100%
|
||||
|
||||
.more
|
||||
text-align: center
|
||||
|
||||
a
|
||||
color: $color-text
|
||||
display: block
|
||||
padding: 25px 0
|
||||
text-decoration: underline
|
||||
width: 100%
|
||||
|
||||
&:hover
|
||||
color: $color-primary
|
||||
|
||||
section.random-asset
|
||||
section.random-asset
|
||||
border-bottom: thin solid $color-background-dark
|
||||
|
||||
ul.random-asset__list
|
||||
@@ -653,10 +446,6 @@ section.announcement
|
||||
|
||||
.title
|
||||
padding-bottom: 10px
|
||||
font:
|
||||
family: $font-body
|
||||
size: 1.4em
|
||||
weight: 300
|
||||
|
||||
+media-xs
|
||||
font-size: 1.4em
|
||||
@@ -693,60 +482,21 @@ section.announcement
|
||||
justify-content: space-around
|
||||
flex-wrap: wrap
|
||||
|
||||
a
|
||||
+button($color-text-light, 3px)
|
||||
padding: 5px 0
|
||||
margin:
|
||||
bottom: 5px
|
||||
right: auto
|
||||
left: auto
|
||||
font-size: .9em
|
||||
opacity: 1
|
||||
flex: 1
|
||||
|
||||
+media-xs
|
||||
margin: 10px auto
|
||||
width: 100%
|
||||
|
||||
&:first-child
|
||||
margin-right: 15px
|
||||
|
||||
&.blue
|
||||
+button(hsl(hue($color-info), 60%, 45%), 3px)
|
||||
|
||||
&.orange
|
||||
+button(hsl(hue($color-secondary), 50%, 50%), 3px)
|
||||
padding: 5px 15px
|
||||
|
||||
&.green
|
||||
+button(hsl(hue($color-success), 60%, 40%), 3px, true)
|
||||
|
||||
|
||||
section.dashboard-in-production
|
||||
.in-production-project
|
||||
border-bottom: thin solid $color-background-dark
|
||||
color: $color-text-dark-primary
|
||||
display: block
|
||||
font-size: 1.1em
|
||||
margin-bottom: 15px
|
||||
|
||||
> img
|
||||
margin-bottom: 15px
|
||||
|
||||
body.homepage
|
||||
.dashboard-container
|
||||
.dashboard-main
|
||||
+media-xs
|
||||
width: 100%
|
||||
background-color: transparent
|
||||
box-shadow: none
|
||||
width: 60%
|
||||
.blog
|
||||
// Custom tweak to Bootstrap grid for the only case when
|
||||
// the post is inside a column (it's usually centered in the page).
|
||||
.col-md-9
|
||||
flex: 1
|
||||
max-width: 100%
|
||||
|
||||
.dashboard-secondary
|
||||
+container-box
|
||||
+media-xs
|
||||
width: 100%
|
||||
width: 38%
|
||||
.jumbotron
|
||||
padding-top: 6em
|
||||
padding-bottom: 6em
|
||||
|
||||
> section
|
||||
padding: 15px
|
||||
*
|
||||
font-size: $h1-font-size
|
||||
|
||||
.lead
|
||||
font-size: $font-size-base
|
||||
|
@@ -10,9 +10,6 @@
|
||||
li a.navbar-item
|
||||
color: $color-text
|
||||
|
||||
.navbar-container
|
||||
+container-behavior
|
||||
|
||||
.navbar-toggle
|
||||
border: 2px solid $color-text-dark-primary
|
||||
color: $color-text
|
||||
@@ -267,22 +264,6 @@
|
||||
+media-xs
|
||||
margin-top: 20px
|
||||
|
||||
.navbar
|
||||
.nav-item-sign-in
|
||||
a.navbar-item
|
||||
background-color: $color-primary
|
||||
border: none
|
||||
border-radius: 3px
|
||||
color: white
|
||||
height: auto
|
||||
font-weight: bold
|
||||
margin-top: 5px
|
||||
margin-left: 10px
|
||||
padding: 10px 20px
|
||||
|
||||
&:hover
|
||||
background-color: lighten($color-primary, 10%)
|
||||
box-shadow: none
|
||||
|
||||
.container.wide-on-sm
|
||||
+media-sm
|
||||
@@ -334,9 +315,7 @@ section.pricing
|
||||
+button($color-primary, 3px, true)
|
||||
|
||||
h3
|
||||
font:
|
||||
size: 1.8em
|
||||
family: $font-body
|
||||
font-size: 1.8em
|
||||
padding-bottom: 0
|
||||
margin: 25px 0 0 10px
|
||||
|
||||
|
@@ -1,25 +1,99 @@
|
||||
@import ../../../pillar/src/styles/_normalize
|
||||
@import ../../../pillar/src/styles/_config
|
||||
@import ../../../pillar/src/styles/_utils
|
||||
// Bootstrap variables and utilities.
|
||||
@import "../../node_modules/bootstrap/scss/functions"
|
||||
@import "../../node_modules/bootstrap/scss/variables"
|
||||
@import "../../node_modules/bootstrap/scss/mixins"
|
||||
|
||||
/* Generic styles (comments, notifications, etc) come from base.css */
|
||||
// Pillar variables and utilities.
|
||||
@import "../../../pillar/src/styles/config"
|
||||
@import "../../../pillar/src/styles/utils"
|
||||
|
||||
|
||||
// Bootstrap components.
|
||||
@import "../../node_modules/bootstrap/scss/root"
|
||||
@import "../../node_modules/bootstrap/scss/reboot"
|
||||
|
||||
@import "../../node_modules/bootstrap/scss/type"
|
||||
@import "../../node_modules/bootstrap/scss/images"
|
||||
|
||||
@import "../../node_modules/bootstrap/scss/code"
|
||||
@import "../../node_modules/bootstrap/scss/grid"
|
||||
@import "../../node_modules/bootstrap/scss/tables"
|
||||
@import "../../node_modules/bootstrap/scss/forms"
|
||||
@import "../../node_modules/bootstrap/scss/buttons"
|
||||
@import "../../node_modules/bootstrap/scss/transitions"
|
||||
@import "../../node_modules/bootstrap/scss/dropdown"
|
||||
@import "../../node_modules/bootstrap/scss/button-group"
|
||||
@import "../../node_modules/bootstrap/scss/input-group"
|
||||
@import "../../node_modules/bootstrap/scss/custom-forms"
|
||||
|
||||
@import "../../node_modules/bootstrap/scss/nav"
|
||||
@import "../../node_modules/bootstrap/scss/navbar"
|
||||
|
||||
@import "../../node_modules/bootstrap/scss/card"
|
||||
@import "../../node_modules/bootstrap/scss/breadcrumb"
|
||||
@import "../../node_modules/bootstrap/scss/pagination"
|
||||
@import "../../node_modules/bootstrap/scss/badge"
|
||||
@import "../../node_modules/bootstrap/scss/jumbotron"
|
||||
@import "../../node_modules/bootstrap/scss/alert"
|
||||
@import "../../node_modules/bootstrap/scss/progress"
|
||||
@import "../../node_modules/bootstrap/scss/media"
|
||||
@import "../../node_modules/bootstrap/scss/list-group"
|
||||
@import "../../node_modules/bootstrap/scss/close"
|
||||
@import "../../node_modules/bootstrap/scss/modal"
|
||||
@import "../../node_modules/bootstrap/scss/tooltip"
|
||||
@import "../../node_modules/bootstrap/scss/popover"
|
||||
@import "../../node_modules/bootstrap/scss/carousel"
|
||||
|
||||
@import "../../node_modules/bootstrap/scss/utilities"
|
||||
@import "../../node_modules/bootstrap/scss/print"
|
||||
|
||||
|
||||
// Pillar components.
|
||||
@import "../../../pillar/src/styles/apps_base"
|
||||
@import "../../../pillar/src/styles/error"
|
||||
|
||||
@import "../../../pillar/src/styles/components/base"
|
||||
|
||||
@import "../../../pillar/src/styles/components/jumbotron"
|
||||
@import "../../../pillar/src/styles/components/alerts"
|
||||
@import "../../../pillar/src/styles/components/navbar"
|
||||
@import "../../../pillar/src/styles/components/dropdown"
|
||||
@import "../../../pillar/src/styles/components/footer"
|
||||
@import "../../../pillar/src/styles/components/shortcode"
|
||||
@import "../../../pillar/src/styles/components/statusbar"
|
||||
@import "../../../pillar/src/styles/components/search"
|
||||
|
||||
@import "../../../pillar/src/styles/components/flyout"
|
||||
@import "../../../pillar/src/styles/components/forms"
|
||||
@import "../../../pillar/src/styles/components/inputs"
|
||||
@import "../../../pillar/src/styles/components/buttons"
|
||||
@import "../../../pillar/src/styles/components/popover"
|
||||
@import "../../../pillar/src/styles/components/tooltip"
|
||||
@import "../../../pillar/src/styles/components/checkbox"
|
||||
@import "../../../pillar/src/styles/components/overlay"
|
||||
@import "../../../pillar/src/styles/components/card"
|
||||
|
||||
@import "../../../pillar/src/styles/comments"
|
||||
@import "../../../pillar/src/styles/notifications"
|
||||
|
||||
/* Blender Cloud specific styles */
|
||||
@import ../../../pillar/src/styles/_project
|
||||
@import ../../../pillar/src/styles/_project-sharing
|
||||
@import ../../../pillar/src/styles/_project-dashboard
|
||||
@import ../../../pillar/src/styles/_user
|
||||
@import "../../../pillar/src/styles/_project"
|
||||
@import "../../../pillar/src/styles/_project-sharing"
|
||||
@import "../../../pillar/src/styles/_project-dashboard"
|
||||
@import "../../../pillar/src/styles/_user"
|
||||
|
||||
@import _welcome
|
||||
@import _homepage
|
||||
@import _services
|
||||
@import ../../../pillar/src/styles/_search
|
||||
@import ../../../pillar/src/styles/_organizations
|
||||
@import _about
|
||||
@import "../../../pillar/src/styles/_search"
|
||||
@import "../../../pillar/src/styles/_organizations"
|
||||
|
||||
/* services, about, etc */
|
||||
@import ../../../pillar/src/styles/_pages
|
||||
@import "../../../pillar/src/styles/_pages"
|
||||
|
||||
/* plugins are included here, don't include in base unless needed by other pillar apps */
|
||||
@import ../../../pillar/src/styles/plugins/_jstree
|
||||
@import ../../../pillar/src/styles/plugins/_js_select2
|
||||
@import "../../../pillar/src/styles/plugins/_jstree"
|
||||
@import "../../../pillar/src/styles/plugins/_js_select2"
|
||||
|
||||
/* CSS for pillar-font comes from fontello.com using static/assets/font/config.json */
|
||||
|
384
src/styles/project-landing.sass
Normal file
@@ -0,0 +1,384 @@
|
||||
// Bootstrap variables and utilities.
|
||||
@import "../../node_modules/bootstrap/scss/functions"
|
||||
@import "../../node_modules/bootstrap/scss/variables"
|
||||
@import "../../node_modules/bootstrap/scss/mixins"
|
||||
|
||||
// Pillar variables and utilities.
|
||||
@import "../../../pillar/src/styles/config"
|
||||
@import "../../../pillar/src/styles/utils"
|
||||
|
||||
// Bootstrap components.
|
||||
@import "../../node_modules/bootstrap/scss/root"
|
||||
@import "../../node_modules/bootstrap/scss/reboot"
|
||||
|
||||
@import "../../node_modules/bootstrap/scss/type"
|
||||
@import "../../node_modules/bootstrap/scss/images"
|
||||
|
||||
@import "../../node_modules/bootstrap/scss/code"
|
||||
@import "../../node_modules/bootstrap/scss/grid"
|
||||
@import "../../node_modules/bootstrap/scss/tables"
|
||||
@import "../../node_modules/bootstrap/scss/forms"
|
||||
@import "../../node_modules/bootstrap/scss/buttons"
|
||||
@import "../../node_modules/bootstrap/scss/transitions"
|
||||
@import "../../node_modules/bootstrap/scss/dropdown"
|
||||
@import "../../node_modules/bootstrap/scss/button-group"
|
||||
@import "../../node_modules/bootstrap/scss/input-group"
|
||||
@import "../../node_modules/bootstrap/scss/custom-forms"
|
||||
|
||||
@import "../../node_modules/bootstrap/scss/nav"
|
||||
@import "../../node_modules/bootstrap/scss/navbar"
|
||||
|
||||
@import "../../node_modules/bootstrap/scss/card"
|
||||
@import "../../node_modules/bootstrap/scss/breadcrumb"
|
||||
@import "../../node_modules/bootstrap/scss/pagination"
|
||||
@import "../../node_modules/bootstrap/scss/badge"
|
||||
@import "../../node_modules/bootstrap/scss/jumbotron"
|
||||
@import "../../node_modules/bootstrap/scss/alert"
|
||||
@import "../../node_modules/bootstrap/scss/progress"
|
||||
@import "../../node_modules/bootstrap/scss/media"
|
||||
@import "../../node_modules/bootstrap/scss/list-group"
|
||||
@import "../../node_modules/bootstrap/scss/close"
|
||||
@import "../../node_modules/bootstrap/scss/modal"
|
||||
@import "../../node_modules/bootstrap/scss/tooltip"
|
||||
@import "../../node_modules/bootstrap/scss/popover"
|
||||
@import "../../node_modules/bootstrap/scss/carousel"
|
||||
|
||||
@import "../../node_modules/bootstrap/scss/utilities"
|
||||
@import "../../node_modules/bootstrap/scss/print"
|
||||
|
||||
|
||||
// Pillar components.
|
||||
@import "../../../pillar/src/styles/apps_base"
|
||||
@import "../../../pillar/src/styles/error"
|
||||
|
||||
@import "../../../pillar/src/styles/components/base"
|
||||
|
||||
@import "../../../pillar/src/styles/components/jumbotron"
|
||||
@import "../../../pillar/src/styles/components/alerts"
|
||||
@import "../../../pillar/src/styles/components/navbar"
|
||||
@import "../../../pillar/src/styles/components/dropdown"
|
||||
@import "../../../pillar/src/styles/components/footer"
|
||||
@import "../../../pillar/src/styles/components/shortcode"
|
||||
@import "../../../pillar/src/styles/components/statusbar"
|
||||
@import "../../../pillar/src/styles/components/search"
|
||||
|
||||
@import "../../../pillar/src/styles/components/flyout"
|
||||
@import "../../../pillar/src/styles/components/inputs"
|
||||
@import "../../../pillar/src/styles/components/buttons"
|
||||
@import "../../../pillar/src/styles/components/popover"
|
||||
@import "../../../pillar/src/styles/components/tooltip"
|
||||
@import "../../../pillar/src/styles/components/checkbox"
|
||||
@import "../../../pillar/src/styles/components/overlay"
|
||||
@import "../../../pillar/src/styles/components/card"
|
||||
|
||||
@import "../../../pillar/src/styles/notifications"
|
||||
|
||||
@import "../../../pillar/src/styles/_search"
|
||||
|
||||
$node-latest-thumbnail-size: 160px
|
||||
$node-latest-gallery-thumbnail-size: 200px
|
||||
|
||||
nav.navbar
|
||||
.navbar-header
|
||||
+media-xs
|
||||
width: 100%
|
||||
|
||||
.navbar-toggle
|
||||
border: none
|
||||
color: $color-text
|
||||
position: absolute
|
||||
right: 10px
|
||||
|
||||
.navbar-nav
|
||||
+media-xs
|
||||
padding: 10px
|
||||
|
||||
.search-input
|
||||
display: none
|
||||
|
||||
.node-details-container
|
||||
max-width: 620px
|
||||
font-size: 1.3em
|
||||
line-height: 1.5em
|
||||
margin: 0 auto 40px auto
|
||||
padding-bottom: 40px
|
||||
|
||||
+media-xs
|
||||
padding-left: 10px
|
||||
padding-right: 10px
|
||||
|
||||
p
|
||||
margin-bottom: 1.3em
|
||||
|
||||
header
|
||||
display: flex
|
||||
flex-direction: column /* stack flex items vertically */
|
||||
position: relative
|
||||
img.header
|
||||
width: 100%
|
||||
flex-direction: column /* stack flex items vertically */
|
||||
position: relative
|
||||
a.page-card-cta
|
||||
position: absolute
|
||||
left: 76%
|
||||
top: 50%
|
||||
transform: translate(-50%, -50%)
|
||||
color: white
|
||||
font-weight: bold
|
||||
background: #ff4970
|
||||
border-radius: 3px
|
||||
border: none
|
||||
box-shadow: 1px 1px 0 rgba(black, .2)
|
||||
padding: 7px 20px
|
||||
text-decoration: none
|
||||
text-shadow: none
|
||||
|
||||
&:hover
|
||||
background: lighten(#ff4970, 5%)
|
||||
|
||||
.landing
|
||||
h2
|
||||
text-align: center
|
||||
margin-bottom: 40px
|
||||
|
||||
section
|
||||
max-width: 1024px
|
||||
padding-top: 20px
|
||||
border-top: thin solid $color-background
|
||||
margin: 0 auto
|
||||
|
||||
.navbar-secondary
|
||||
max-width: 620px
|
||||
margin: 0 auto
|
||||
|
||||
.navbar-collapse
|
||||
padding-left: 0
|
||||
|
||||
li
|
||||
a
|
||||
padding-left: 20px
|
||||
padding-right: 20px
|
||||
color: $color-text
|
||||
&:hover
|
||||
&.active
|
||||
background: none
|
||||
color: black
|
||||
box-shadow: 0px 2px 0 rgba(red, .8)
|
||||
|
||||
.node-extra
|
||||
display: flex
|
||||
flex-direction: column
|
||||
|
||||
//padding: 0 20px
|
||||
width: 100%
|
||||
|
||||
|
||||
.node-updates
|
||||
flex: 1
|
||||
font-size: 1.1em
|
||||
|
||||
ul
|
||||
padding: 0
|
||||
margin: 0 0 15px 0
|
||||
display: flex
|
||||
flex-direction: row
|
||||
flex-wrap: wrap
|
||||
|
||||
li
|
||||
display: flex
|
||||
flex-direction: column
|
||||
list-style: none
|
||||
padding: 5px
|
||||
cursor: pointer
|
||||
width: 33.3333%
|
||||
|
||||
+media-xs
|
||||
width: 100%
|
||||
|
||||
&.texture, &.group_texture
|
||||
width: 25%
|
||||
|
||||
&:hover
|
||||
img
|
||||
opacity: .9
|
||||
a.title
|
||||
//color: $color-primary
|
||||
text-decoration: underline
|
||||
|
||||
&.post
|
||||
.info .title
|
||||
//color: $node-type-post
|
||||
font-size: 1.1em
|
||||
a.image
|
||||
border: none
|
||||
//border-color: $node-type-post
|
||||
background-color: hsl(hue($node-type-post), 20%, 55%)
|
||||
|
||||
&.asset.image a.image
|
||||
border-color: $node-type-asset_image
|
||||
background-color: hsl(hue($node-type-asset_image), 20%, 55%)
|
||||
&.asset.file a.image
|
||||
border-color: $node-type-asset_file
|
||||
background-color: hsl(hue($node-type-asset_file), 20%, 55%)
|
||||
&.asset.video a.image
|
||||
border-color: $node-type-asset_video
|
||||
background-color: hsl(hue($node-type-asset_video), 20%, 55%)
|
||||
|
||||
.image
|
||||
width: 100%
|
||||
height: $node-latest-thumbnail-size
|
||||
min-height: $node-latest-thumbnail-size
|
||||
max-height: $node-latest-thumbnail-size
|
||||
background-color: $color-background
|
||||
margin: 5px auto 10px auto
|
||||
position: relative
|
||||
overflow: hidden
|
||||
border-radius: 0
|
||||
|
||||
img
|
||||
max-height: $node-latest-thumbnail-size
|
||||
+position-center-translate
|
||||
|
||||
i
|
||||
color: rgba(white, .9)
|
||||
font-size: 1.8em
|
||||
position: absolute
|
||||
bottom: 3px
|
||||
left: 5px
|
||||
text-shadow: 1px 1px 0 rgba(black, .2)
|
||||
|
||||
&.pi-file-archive
|
||||
font-size: 1.5em
|
||||
bottom: 5px
|
||||
&.pi-newspaper
|
||||
font-size: 1.6em
|
||||
left: 7px
|
||||
|
||||
.ribbon
|
||||
+ribbon
|
||||
|
||||
.info
|
||||
width: 100%
|
||||
height: 100%
|
||||
display: flex
|
||||
flex-direction: column
|
||||
justify-content: space-between
|
||||
word-break: break-word
|
||||
|
||||
.description
|
||||
font-size: 1em
|
||||
line-height: 1.8em
|
||||
padding-top: 8px
|
||||
color: $color-text-dark-primary
|
||||
|
||||
.title
|
||||
display: block
|
||||
font-size: 1.3em
|
||||
color: $color-text-dark
|
||||
font-weight: 600
|
||||
+clearfix
|
||||
+text-overflow-ellipsis
|
||||
|
||||
span.details
|
||||
width: 100%
|
||||
display: block
|
||||
font-size: 1em
|
||||
line-height: 1.2em
|
||||
padding: 5px 0
|
||||
color: $color-text-dark-secondary
|
||||
+clearfix
|
||||
|
||||
.who
|
||||
margin-left: 3px
|
||||
.what
|
||||
text-transform: capitalize
|
||||
|
||||
|
||||
$bg-color: #444
|
||||
$bg-color2: #666
|
||||
$yellow: rgb(249,229,89)
|
||||
$almost-white: rgb(255,255,255)
|
||||
$btn-transparent-color: rgba(249,229,89,1)
|
||||
$btn-transparent-bg: rgba(249,229,89,0)
|
||||
|
||||
|
||||
section.gallery
|
||||
max-width: 1024px
|
||||
margin: 60px auto 0 auto
|
||||
text-align: center
|
||||
padding-bottom: 40px
|
||||
|
||||
p
|
||||
color: $almost-white
|
||||
padding: 0 40px
|
||||
|
||||
|
||||
.thumbnail
|
||||
float: left
|
||||
position: relative
|
||||
width: 23%
|
||||
padding-bottom: 23%
|
||||
margin: 0.83%
|
||||
overflow: hidden
|
||||
&:hover
|
||||
box-shadow: 2px 2px 50px 0 rgba(0,0,0,0.3)
|
||||
|
||||
.img-container
|
||||
position: absolute
|
||||
width: 100%
|
||||
height: 100%
|
||||
|
||||
img
|
||||
width: 300%
|
||||
transform: translate(-20%,-10%)
|
||||
|
||||
&:hover .img-caption
|
||||
top: 0
|
||||
left: 0
|
||||
.btn-trans
|
||||
background: rgba(255,255,255,0.4)
|
||||
|
||||
.img-caption
|
||||
position: absolute
|
||||
width: 100%
|
||||
height: 100%
|
||||
background: rgba(0, 0, 0, 0.3)
|
||||
text-align: center
|
||||
|
||||
.table
|
||||
display: table
|
||||
.table-cell
|
||||
display: table-cell
|
||||
vertical-align: bottom
|
||||
border: none
|
||||
|
||||
@media screen and (max-width: 992px)
|
||||
.thumbnail
|
||||
width: 22%
|
||||
padding-bottom: 22%
|
||||
margin: 1.5%
|
||||
|
||||
.img-container:hover .img-caption
|
||||
top: 0
|
||||
left: 0
|
||||
|
||||
.img-caption
|
||||
position: absolute
|
||||
width: 100%
|
||||
height: 100%
|
||||
background: rgba(0, 0, 0, .7)
|
||||
text-align: center
|
||||
a
|
||||
color: $yellow
|
||||
|
||||
@media screen and (max-width: 720px)
|
||||
.thumbnail
|
||||
width: 29%
|
||||
padding-bottom: 29%
|
||||
margin: 2.16%
|
||||
|
||||
@media screen and (max-width: 470px)
|
||||
.thumbnail
|
||||
width: 44%
|
||||
padding-bottom: 44%
|
||||
margin: 3%
|
26
src/templates/_macros/_navigation.pug
Normal file
@@ -0,0 +1,26 @@
|
||||
include ../mixins/components
|
||||
|
||||
| {% macro navigation_tabs(title) %}
|
||||
+nav-secondary()
|
||||
+nav-secondary-link(
|
||||
class="{% if title == 'homepage' %}active{% endif %}",
|
||||
href="{{ url_for('main.homepage') }}")
|
||||
| Activity
|
||||
|
||||
+nav-secondary-link(
|
||||
class="{% if title == 'home' %}active{% endif %}",
|
||||
href="{{ url_for('projects.home_project') }}")
|
||||
| Home
|
||||
|
||||
+nav-secondary-link(
|
||||
class="{% if title == 'dashboard' %}active{% endif %}",
|
||||
href="{{ url_for('projects.index') }}")
|
||||
| My Projects
|
||||
|
||||
| {% if current_user.has_organizations() %}
|
||||
+nav-secondary-link(
|
||||
class="{% if title == 'organizations' %}active{% endif %}",
|
||||
href="{{ url_for('pillar.web.organizations.index') }}")
|
||||
| My Organizations
|
||||
| {% endif %}
|
||||
| {% endmacro %}
|
@@ -24,6 +24,148 @@ style.
|
||||
br
|
||||
| unique set of learning and creative resources.
|
||||
#page-content
|
||||
|
||||
section.team
|
||||
.container
|
||||
h2.
|
||||
Meet a restless team of artists and developers <br/>
|
||||
wants to share their work with you.
|
||||
|
||||
.people-container
|
||||
.people-intro
|
||||
h3 Blender Institute
|
||||
span Amsterdam, The Netherlands
|
||||
|
||||
.row
|
||||
.col-md-3
|
||||
a.face(
|
||||
href="https://twitter.com/tonroosendaal",
|
||||
data-blenderhead='ton')
|
||||
img(alt="Ton", src="{{ url_for('static', filename='assets/img/people/ton.jpg')}}")
|
||||
.bio
|
||||
h3 Ton Roosendaal
|
||||
small CEO Blender Foundation. Producer Blender Institute
|
||||
span The Netherlands
|
||||
.col-md-3
|
||||
a.face(
|
||||
href="https://twitter.com/fsiddi",
|
||||
data-blenderhead='francesco')
|
||||
img(alt="Francesco", src="{{ url_for('static', filename='assets/img/people/francesco.jpg')}}")
|
||||
.bio
|
||||
h3 Francesco Siddi
|
||||
small Pipeline Tools & Back-end Web Development
|
||||
span Italy
|
||||
.col-md-3
|
||||
a.face(
|
||||
href="https://twitter.com/hjalti",
|
||||
data-blenderhead='hjalti')
|
||||
img(alt="Hjalti", src="{{ url_for('static', filename='assets/img/people/hjalti.jpg')}}")
|
||||
.bio
|
||||
h3 Hjalti Hjálmarsson
|
||||
small Director. Animation. Layout.
|
||||
span Iceland
|
||||
.col-md-3
|
||||
a.face(
|
||||
href="https://twitter.com/PabloVazquez_",
|
||||
data-blenderhead='pablo')
|
||||
img(alt="Pablo", src="{{ url_for('static', filename='assets/img/people/pablo.jpg')}}")
|
||||
.bio
|
||||
h3 Pablo Vázquez
|
||||
small Lighting, Rendering. Front-end Web Development
|
||||
span Argentina
|
||||
.row
|
||||
.col-md-3
|
||||
a.face(
|
||||
href="https://twitter.com/artificial3d",
|
||||
data-blenderhead='andy')
|
||||
img(alt="Andy", src="{{ url_for('static', filename='assets/img/people/andy.jpg')}}")
|
||||
.bio
|
||||
h3 Andy Goralczyk
|
||||
small Shading, Lighting, Rendering, FX
|
||||
span Germany
|
||||
.col-md-3
|
||||
a.face(
|
||||
href="https://developer.blender.org/p/sergey/",
|
||||
data-blenderhead='sergey')
|
||||
img(alt="Sergey", src="{{ url_for('static', filename='assets/img/people/sergey.jpg')}}")
|
||||
.bio
|
||||
h3 Sergey Sharybin
|
||||
small Blender & Cycles Core Developer
|
||||
span Russia
|
||||
.col-md-3
|
||||
a.face(
|
||||
href="https://twitter.com/sastuvel",
|
||||
data-blenderhead='sybren')
|
||||
img(alt="Sybren", src="{{ url_for('static', filename='assets/img/people/sybren.jpg')}}")
|
||||
.bio
|
||||
h3 Sybren Stüvel
|
||||
small Blender Cloud Developer
|
||||
span The Netherlands
|
||||
.col-md-3
|
||||
a.face(
|
||||
href="https://twitter.com/dfelinto",
|
||||
data-blenderhead='dalai')
|
||||
img(alt="dalai", src="{{ url_for('static', filename='assets/img/people/dalai.jpg')}}")
|
||||
.bio
|
||||
h3 Dalai Felinto
|
||||
small Blender Developer
|
||||
span Brazil
|
||||
|
||||
.people-container.online
|
||||
.people-intro
|
||||
h3 Online Collaborators
|
||||
span Contributing to Blender Cloud from all over the globe.
|
||||
.row
|
||||
.col-md-3
|
||||
a.face(
|
||||
href="https://twitter.com/davidrevoy",
|
||||
data-blenderhead='david')
|
||||
img(alt="David", src="{{ url_for('static', filename='assets/img/people/david.jpg')}}")
|
||||
.bio
|
||||
h3 David Revoy
|
||||
small Illustrator & Concept Artist
|
||||
span France
|
||||
.col-md-3
|
||||
a.face(
|
||||
href="https://twitter.com/s_koenig",
|
||||
data-blenderhead='sebastian')
|
||||
img(alt="Sebastian", src="{{ url_for('static', filename='assets/img/people/sebastian.jpg')}}")
|
||||
.bio
|
||||
h3 Sebastian König
|
||||
small VFX
|
||||
span Germany
|
||||
.col-md-3
|
||||
a.face(
|
||||
href="https://twitter.com/gleb_alexandrov",
|
||||
data-blenderhead='gleb')
|
||||
img(alt="Gleb", src="{{ url_for('static', filename='assets/img/people/gleb.jpg')}}")
|
||||
.bio
|
||||
h3 Gleb Alexandrov
|
||||
small Lighting & Shading
|
||||
span Belarus
|
||||
.col-md-3
|
||||
a.face(
|
||||
href="https://twitter.com/the_mantissa",
|
||||
data-blenderhead='midge')
|
||||
img(alt="Midge", src="{{ url_for('static', filename='assets/img/people/midge.jpg')}}")
|
||||
.bio
|
||||
h3 Midge Sinnaeve
|
||||
small Motion Graphics
|
||||
span Belgium
|
||||
|
||||
.row
|
||||
.col-md-3
|
||||
a.face(
|
||||
href="https://twitter.com/jpbouza",
|
||||
data-blenderhead='jpbouza')
|
||||
img(alt="Juan Pablo", src="{{ url_for('static', filename='assets/img/people/jpbouza.jpg')}}")
|
||||
.bio
|
||||
h3 Juan Pablo Bouza
|
||||
small Rigging
|
||||
span Argentina
|
||||
|
||||
section.page-card
|
||||
h2 A bit of History
|
||||
section.page-card
|
||||
.page-card-side
|
||||
h2.page-card-title
|
||||
@@ -33,11 +175,11 @@ style.
|
||||
| First happy cloud video and crowdfunding for Cosmos Laundromat Pilot.
|
||||
.page-card-side
|
||||
a(href='https://gooseberry.blender.org/gooseberry-campaign-launched-we-need-10k-people-to-help/')
|
||||
img.img-responsive(src="{{ url_for('static_cloud', filename='img/2014_03_09_sxsw.jpg') }}")
|
||||
img.img-responsive(src="{{ url_for('static_cloud', filename='img/2014_03_09_sxsw.jpg') }}", alt="SXSW")
|
||||
section.page-card
|
||||
.page-card-side
|
||||
a(href='https://gooseberry.blender.org/gooseberry-campaign-launched-we-need-10k-people-to-help/')
|
||||
img.img-responsive(src="{{ url_for('static_cloud', filename='img/2014_03_10_cosmos.jpg') }}")
|
||||
img.img-responsive(src="{{ url_for('static_cloud', filename='img/2014_03_10_cosmos.jpg') }}", alt="Cosmos Laundromat")
|
||||
.page-card-side
|
||||
h2.page-card-title
|
||||
| Gooseberry | Cosmos Laundromat
|
||||
@@ -57,11 +199,11 @@ style.
|
||||
| .
|
||||
.page-card-side
|
||||
a(href='https://cloud.blender.org/p/glass-half/blog/glass-half-premiere')
|
||||
img.img-responsive(src="{{ url_for('static_cloud', filename='img/2015_10_30_glass.jpg') }}")
|
||||
img.img-responsive(src="{{ url_for('static_cloud', filename='img/2015_10_30_glass.jpg') }}", alt="Glass Half")
|
||||
section.page-card
|
||||
.page-card-side
|
||||
a(href='https://cloud.blender.org/blog/new-art-gallery-with-gleb-alexandrov')
|
||||
img.img-responsive(src="{{ url_for('static_cloud', filename='img/2015_11_19_art.jpg') }}")
|
||||
img.img-responsive(src="{{ url_for('static_cloud', filename='img/2015_11_19_art.jpg') }}", alt="Art Gallery")
|
||||
.page-card-side
|
||||
h2.page-card-title
|
||||
| Art Gallery
|
||||
@@ -77,11 +219,11 @@ style.
|
||||
| With so much going on in the Cloud at at the studio. The Blender Institute Podcast was born! Sharing our daily studio work, Blender community news, and interacting with the awesome Blender Cloud subscribers.
|
||||
.page-card-side
|
||||
a(href='https://cloud.blender.org/blog/introducing-blender-institute-podcast')
|
||||
img.img-responsive(src="{{ url_for('static_cloud', filename='img/2015_11_24_bip.jpg') }}")
|
||||
img.img-responsive(src="{{ url_for('static_cloud', filename='img/2015_11_24_bip.jpg') }}", alt="Blender Institute Podcast")
|
||||
section.page-card
|
||||
.page-card-side
|
||||
a(href='https://cloud.blender.org/p/blenrig/blog/welcome-to-the-blenrig-project')
|
||||
img.img-responsive(src="{{ url_for('static_cloud', filename='img/2015_12_01_blenrig.jpg') }}")
|
||||
img.img-responsive(src="{{ url_for('static_cloud', filename='img/2015_12_01_blenrig.jpg') }}", alt="Blenrig")
|
||||
.page-card-side
|
||||
h2.page-card-title
|
||||
| Blenrig
|
||||
@@ -97,11 +239,11 @@ style.
|
||||
| The biggest source for CC0/Public Domain textures on the interwebs goes live. First as beta, as a quick gift right before Xmas 2015!
|
||||
.page-card-side
|
||||
a(href='https://cloud.blender.org/blog/new-texture-library')
|
||||
img.img-responsive(src="{{ url_for('static_cloud', filename='img/2015_12_23_textures.jpg') }}")
|
||||
img.img-responsive(src="{{ url_for('static_cloud', filename='img/2015_12_23_textures.jpg') }}", alt="Texture Library")
|
||||
section.page-card
|
||||
.page-card-side
|
||||
a(href='https://cloud.blender.org/blog/nraryew-the-character-lib')
|
||||
img.img-responsive(src="{{ url_for('static_cloud', filename='img/2016_01_05_charlib.jpg') }}")
|
||||
img.img-responsive(src="{{ url_for('static_cloud', filename='img/2016_01_05_charlib.jpg') }}", alt="Character Library")
|
||||
.page-card-side
|
||||
h2.page-card-title
|
||||
| Character Library
|
||||
@@ -121,11 +263,11 @@ style.
|
||||
| .
|
||||
.page-card-side
|
||||
a(href='https://cloud.blender.org/p/caminandes-3/blog/caminandes-llamigos')
|
||||
img.img-responsive(src="{{ url_for('static_cloud', filename='img/2016_01_30_llamigos.jpg') }}")
|
||||
img.img-responsive(src="{{ url_for('static_cloud', filename='img/2016_01_30_llamigos.jpg') }}", alt="Caminandes: Llamigos")
|
||||
section.page-card
|
||||
.page-card-side
|
||||
a(href='https://cloud.blender.org/blog/welcome-sybren')
|
||||
img.img-responsive(src="{{ url_for('static_cloud', filename='img/2016_03_01_sybren.jpg') }}")
|
||||
img.img-responsive(src="{{ url_for('static_cloud', filename='img/2016_03_01_sybren.jpg') }}", alt="Dr. Sybren!")
|
||||
.page-card-side
|
||||
h2.page-card-title
|
||||
| Sybren
|
||||
@@ -141,11 +283,11 @@ style.
|
||||
| Create your own private projects on Blender Cloud.
|
||||
.page-card-side
|
||||
a(href='https://cloud.blender.org/blog/welcome-sybren')
|
||||
img.img-responsive(src="{{ url_for('static_cloud', filename='img/2016_05_03_projects.jpg') }}")
|
||||
img.img-responsive(src="{{ url_for('static_cloud', filename='img/2016_05_03_projects.jpg') }}", alt="Projects")
|
||||
section.page-card
|
||||
.page-card-side
|
||||
a(href='https://cloud.blender.org/blog/introducing-project-sharing')
|
||||
img.img-responsive(src="{{ url_for('static_cloud', filename='img/2016_05_09_projectsharing.jpg') }}")
|
||||
img.img-responsive(src="{{ url_for('static_cloud', filename='img/2016_05_09_projectsharing.jpg') }}", alt="Sharing")
|
||||
.page-card-side
|
||||
h2.page-card-title
|
||||
| Project Sharing
|
||||
@@ -161,11 +303,11 @@ style.
|
||||
| Browse the textures from within Blender!
|
||||
.page-card-side
|
||||
a(href='https://cloud.blender.org/blog/introducing-project-sharing')
|
||||
img.img-responsive(src="{{ url_for('static_cloud', filename='img/2016_05_11_addon.jpg') }}")
|
||||
img.img-responsive(src="{{ url_for('static_cloud', filename='img/2016_05_11_addon.jpg') }}", alt="Blender Cloud Add-on")
|
||||
section.page-card
|
||||
.page-card-side
|
||||
a(href='https://cloud.blender.org/blog/introducing-private-texture-libraries')
|
||||
img.img-responsive(src="{{ url_for('static_cloud', filename='img/2016_05_23_privtextures.jpg') }}")
|
||||
img.img-responsive(src="{{ url_for('static_cloud', filename='img/2016_05_23_privtextures.jpg') }}", alt="Texture Libraries")
|
||||
.page-card-side
|
||||
h2.page-card-title
|
||||
| Private Texture Libraries
|
||||
@@ -181,11 +323,11 @@ style.
|
||||
| Sync your Blender preferences across multiple devices.
|
||||
.page-card-side
|
||||
a(href='https://cloud.blender.org/blog/introducing-blender-sync')
|
||||
img.img-responsive(src="{{ url_for('static_cloud', filename='img/2016_06_30_sync.jpg') }}")
|
||||
img.img-responsive(src="{{ url_for('static_cloud', filename='img/2016_06_30_sync.jpg') }}", alt="Blender Sync")
|
||||
section.page-card
|
||||
.page-card-side
|
||||
a(href='https://cloud.blender.org/blog/introducing-image-sharing')
|
||||
img.img-responsive(src="{{ url_for('static_cloud', filename='img/2016_07_14_image.jpg') }}")
|
||||
img.img-responsive(src="{{ url_for('static_cloud', filename='img/2016_07_14_image.jpg') }}", alt="Image Sharing")
|
||||
.page-card-side
|
||||
h2.page-card-title
|
||||
| Image Sharing
|
||||
@@ -202,11 +344,11 @@ style.
|
||||
| High-dynamic range images are now available on Blender Cloud! With their own special viewer. Also available via the Blender Cloud add-on.
|
||||
.page-card-side
|
||||
a(href='https://cloud.blender.org/blog/introducing-the-hdri-library')
|
||||
img.img-responsive(src="{{ url_for('static_cloud', filename='img/2016_07_27_hdri.jpg') }}")
|
||||
img.img-responsive(src="{{ url_for('static_cloud', filename='img/2016_07_27_hdri.jpg') }}", alt="HDRI Library")
|
||||
section.page-card
|
||||
.page-card-side
|
||||
a(href='https://cloud.blender.org/blog/introducing-the-hdri-library')
|
||||
img.img-responsive(src="{{ url_for('static_cloud', filename='img/2016_12_06_toon.jpg') }}")
|
||||
img.img-responsive(src="{{ url_for('static_cloud', filename='img/2016_12_06_toon.jpg') }}", alt="Hdri Library")
|
||||
.page-card-side
|
||||
h2.page-card-title
|
||||
a(href='https://cloud.blender.org/blog/new-training-toon-character-workflow')
|
||||
@@ -225,5 +367,8 @@ style.
|
||||
a.page-card-cta(href='https://store.blender.org/product/membership/') Subscribe
|
||||
.page-card-side
|
||||
a(href='https://cloud.blender.org/p/agent-327')
|
||||
img.img-responsive(src="{{ url_for('static_cloud', filename='img/2017_03_10_agent.jpg') }}")
|
||||
img.img-responsive(src="{{ url_for('static_cloud', filename='img/2017_03_10_agent.jpg') }}", alt="Agent 327")
|
||||
|
||||
|
||||
|
||||
| {% endblock body%}
|
||||
|
@@ -18,59 +18,51 @@ meta(property="og:image", content="{% if main_project.picture_header %}{{ main_p
|
||||
meta(name="twitter:image", content="{% if main_project.picture_header %}{{ main_project.picture_header.thumbnail('l', api=api) }}{% else %}{{ url_for('static', filename='assets/img/backgrounds/background_agent327_04.jpg')}}{% endif %}")
|
||||
| {% endblock %}
|
||||
|
||||
| {% block body %}
|
||||
.dashboard-container
|
||||
section.dashboard-main
|
||||
| {% block navigation_tabs %}
|
||||
| {{ navigation_tabs(title) }}
|
||||
| {% endblock navigation_tabs %}
|
||||
|
||||
section.blog-stream
|
||||
ul.blog-stream__list
|
||||
| {% block body %}
|
||||
.container-fluid.dashboard-container.imgs-fluid
|
||||
.row
|
||||
.col-md-8
|
||||
section.blog
|
||||
ul.list-unstyled
|
||||
| {% if latest_posts %}
|
||||
| {% for node in latest_posts %}
|
||||
| {{ render_blog_post(node) }}
|
||||
| {% endfor %}
|
||||
| {% else %}
|
||||
li
|
||||
.blog-stream__list-details
|
||||
ul.meta
|
||||
li.when No blog entries... yet!
|
||||
| No blog entries... yet!
|
||||
| {% endif %}
|
||||
|
||||
.more
|
||||
a(href="{{ url_for('main.main_blog') }}")
|
||||
.d-block.text-center
|
||||
a.d-inline-block.p-3.text-muted(href="{{ url_for('main.main_blog') }}")
|
||||
| See All Blog Posts
|
||||
|
||||
a.feed(
|
||||
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
|
||||
|
||||
|
||||
section.dashboard-secondary
|
||||
| {{ navigation_tabs(title) }}
|
||||
|
||||
section.dashboard-in-production
|
||||
h4 In Production
|
||||
span.section-lead.
|
||||
Check out these projects currently in production!
|
||||
|
||||
a.in-production-project(href="https://cloud.blender.org/p/spring/")
|
||||
.col-md-4
|
||||
.dashboard-sidebar
|
||||
section.pt-3
|
||||
h6.title-underline In Production
|
||||
a(href="/p/spring/")
|
||||
img(src="{{ url_for('static', filename='assets/img/projects/spring_450x150.jpg')}}")
|
||||
p.
|
||||
#[strong Spring] - A poetic short film about a mountain spirit and her wise little dog.
|
||||
|
||||
a.in-production-project(href="https://cloud.blender.org/p/hero/")
|
||||
img(src="{{ url_for('static', filename='assets/img/projects/hero_450x150.jpg')}}")
|
||||
p.
|
||||
#[strong Hero] - A '2D' trailer-style movie focused on getting grease pencil
|
||||
production ready for Blender 2.8.
|
||||
p.text-muted.pt-2.
|
||||
A poetic short film about a mountain spirit and her wise little dog. #[a(href="/p/spring/") Check it out].
|
||||
|
||||
section.stream
|
||||
section.stream.py-3
|
||||
h6.title-underline Latest Assets
|
||||
|
||||
h4 Latest Assets
|
||||
|
||||
ul.activity-stream__list
|
||||
ul.activity-stream__list.list-unstyled
|
||||
| {% for n in activity_stream %}
|
||||
li(
|
||||
class="{{ n.node_type }} {{ n.properties.content_type }} {% if n.picture %}with-picture{% endif %}",
|
||||
@@ -104,7 +96,7 @@ meta(name="twitter:image", content="{% if main_project.picture_header %}{{ main_
|
||||
.ribbon
|
||||
span free
|
||||
| {% endif %}
|
||||
ul.meta
|
||||
ul.list-unstyled.d-flex.text-muted
|
||||
| {% if not n.picture %}
|
||||
li.when
|
||||
a(href="{{ n.url }}", title="{{ n._created }}") {{ n._created | pretty_date_time }}
|
||||
@@ -123,7 +115,7 @@ meta(name="twitter:image", content="{% if main_project.picture_header %}{{ main_
|
||||
| {% endif %}
|
||||
|
||||
| {% if n.picture %}
|
||||
ul.meta.extra
|
||||
ul.list-unstyled.d-flex.text-muted.extra
|
||||
li.when
|
||||
a(href="{{ n.url }}", title="{{ n._created }}") {{ n._created | pretty_date_time }}
|
||||
li.who {{ n.user.full_name }}
|
||||
@@ -134,12 +126,12 @@ meta(name="twitter:image", content="{% if main_project.picture_header %}{{ main_
|
||||
| No items to list.
|
||||
|
||||
|
||||
section.random-asset
|
||||
h4
|
||||
section.random-asset.py-3
|
||||
h6.title-underline
|
||||
a(href="/search") Explore the Cloud
|
||||
span.section-lead Random selection of the best assets & tutorials
|
||||
.pb-3.text-muted Random selection of the best assets & tutorials
|
||||
|
||||
ul.random-asset__list
|
||||
ul.random-asset__list.list-unstyled
|
||||
| {% for n in random_featured %}
|
||||
| {% if n.picture and loop.first %}
|
||||
li.random-asset__list-item.project
|
||||
@@ -151,7 +143,7 @@ meta(name="twitter:image", content="{% if main_project.picture_header %}{{ main_
|
||||
.random-asset__list-details
|
||||
a.title(href="{{ n.project.url }}") {{ n.project.name }}
|
||||
| {% if n.project.summary %}
|
||||
ul.meta
|
||||
ul.list-unstyled.d-flex.text-muted
|
||||
li.what
|
||||
a(href="{{ n.project.url }}") {{ n.project.summary }}
|
||||
| {% endif %}
|
||||
@@ -175,7 +167,7 @@ meta(name="twitter:image", content="{% if main_project.picture_header %}{{ main_
|
||||
|
||||
a.title(href="{{ n.url }}")
|
||||
| {{ n.name }}
|
||||
ul.meta
|
||||
ul.list-unstyled.d-flex.text-muted
|
||||
li.what
|
||||
a(href="{{ n.url }}")
|
||||
| {% if n.properties.content_type %}{{ n.properties.content_type | undertitle }}{% else %}Folder{% endif %}
|
||||
@@ -207,7 +199,7 @@ meta(name="twitter:image", content="{% if main_project.picture_header %}{{ main_
|
||||
| {% endif %}
|
||||
.random-asset__list-details
|
||||
a.title(href="{{ n.url }}") {{ n.name }}
|
||||
ul.meta
|
||||
ul.list-unstyled.d-flex.text-muted
|
||||
li.what
|
||||
a(href="{{ n.url }}")
|
||||
| {% if n.properties.content_type %}{{ n.properties.content_type }}{% else %}Folder{% endif %}
|
||||
@@ -218,11 +210,10 @@ meta(name="twitter:image", content="{% if main_project.picture_header %}{{ main_
|
||||
| {% endfor %}
|
||||
|
||||
|
||||
section.comments
|
||||
section.comments.py-3
|
||||
h6.title-underline Latest Comments
|
||||
|
||||
h4 Latest Comments
|
||||
|
||||
ul
|
||||
ul.list-unstyled
|
||||
| {% if latest_comments %}
|
||||
| {% for n in latest_comments %}
|
||||
li(
|
||||
@@ -232,7 +223,7 @@ meta(name="twitter:image", content="{% if main_project.picture_header %}{{ main_
|
||||
a.comment-content(href="{{ n.url }}")
|
||||
| {{ n.properties.content | striptags | truncate(200) }}
|
||||
|
||||
ul.meta
|
||||
ul.list-unstyled.d-flex.text-muted
|
||||
li.who {{ n.user.full_name }}
|
||||
| {% if n.attached_to %}
|
||||
|
||||
|
@@ -3,7 +3,7 @@ html(lang="en")
|
||||
head
|
||||
meta(charset="utf-8")
|
||||
title {% if self.page_title() %}{% block page_title %}{% endblock %} — {% endif %}Blender Cloud
|
||||
meta(name="viewport", content="width=device-width, initial-scale=1.0")
|
||||
meta(name="viewport", content="width=device-width, initial-scale=1, shrink-to-fit=no")
|
||||
meta(name="description", content="Blender Cloud is a web based service developed by Blender Institute that allows people to access the training videos and all the data from the open projects.")
|
||||
meta(name="author", content="Blender Institute")
|
||||
meta(name="theme-color", content="#3e92aa")
|
||||
@@ -29,45 +29,38 @@ html(lang="en")
|
||||
meta(name="twitter:image", content="{{ url_for('static', filename='assets/img/backgrounds/background_gleb_locomotive.jpg')}}")
|
||||
| {% endblock og %}
|
||||
|
||||
script(src="{{ url_for('static_pillar', filename='assets/js/vendor/jquery-3.1.0.min.js', v=9112017)}}")
|
||||
script(src="{{ url_for('static_pillar', filename='assets/js/vendor/jquery.typeahead-0.11.1.min.js', v=9112017)}}")
|
||||
script(src="{{ url_for('static_pillar', filename='assets/js/vendor/js.cookie-2.0.3.min.js', v=9112017)}}")
|
||||
|
||||
script.
|
||||
script(src="{{ url_for('static_pillar', filename='assets/js/vendor/jquery-3.1.0.min.js')}}")
|
||||
script(src="{{ url_for('static_pillar', filename='assets/js/vendor/jquery.typeahead-0.11.1.min.js')}}")
|
||||
script(src="{{ url_for('static_pillar', filename='assets/js/vendor/js.cookie-2.0.3.min.js')}}")
|
||||
| {% if current_user.is_authenticated %}
|
||||
script(src="{{ url_for('static_pillar', filename='assets/js/vendor/clipboard.min.js')}}")
|
||||
| {% endif %}
|
||||
|
||||
| {% if current_user.has_cap('subscriber') %}
|
||||
| {# Only load if we can comment (for converting markdown as-we-type) #}
|
||||
script(src="{{ url_for('static_pillar', filename='assets/js/markdown.min.js', v=9112017) }}")
|
||||
script(src="{{ url_for('static_pillar', filename='assets/js/markdown.min.js') }}")
|
||||
| {% endif %}
|
||||
|
||||
script(src="{{ url_for('static_pillar', filename='assets/js/tutti.min.js', v=9112017) }}")
|
||||
script(src="{{ url_for('static_pillar', filename='assets/js/tutti.min.js') }}")
|
||||
|
||||
link(href="{{ url_for('static', filename='assets/img/favicon.png') }}", rel="shortcut icon")
|
||||
link(href="{{ url_for('static', filename='assets/img/apple-touch-icon-precomposed.png') }}", rel="icon apple-touch-icon-precomposed", sizes="192x192")
|
||||
|
||||
link(href="{{ url_for('static_pillar', filename='assets/css/vendor/bootstrap.min.css', v=9112017) }}", rel="stylesheet")
|
||||
link(href="{{ url_for('static', filename='assets/google-font-roboto/roboto.css', v=9112017) }}", rel="stylesheet")
|
||||
|
||||
| {% block head %}{% endblock %}
|
||||
|
||||
| {% block css %}
|
||||
link(href="{{ url_for('static_pillar', filename='assets/css/font-pillar.css', v=9112017) }}", rel="stylesheet")
|
||||
link(href="{{ url_for('static_pillar', filename='assets/css/base.css', v=9112017) }}", rel="stylesheet")
|
||||
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', v=9112017) }}", rel="stylesheet")
|
||||
link(href="{{ url_for('static_pillar', filename='assets/css/blog.css') }}", rel="stylesheet")
|
||||
| {% else %}
|
||||
link(href="{{ url_for('static', filename='cloud/assets/css/main.css', v=9112017) }}", rel="stylesheet")
|
||||
link(href="{{ url_for('static_cloud', filename='assets/css/main.css') }}", rel="stylesheet")
|
||||
| {% endif %}
|
||||
| {% endblock css %}
|
||||
|
||||
|
||||
| {% if not title %}{% set title="default" %}{% endif %}
|
||||
|
||||
body(class="{{ title }}")
|
||||
.container-page
|
||||
| {% with messages = get_flashed_messages(with_categories=True) %}
|
||||
| {% if messages %}
|
||||
|
||||
| {% for (category, message) in messages %}
|
||||
.alert(role="alert", class="alert-{{ category }}")
|
||||
i.alert-icon(class="{{ category }}")
|
||||
@@ -75,23 +68,29 @@ html(lang="en")
|
||||
button.close(type="button", data-dismiss="alert")
|
||||
i.pi-cancel
|
||||
| {% endfor %}
|
||||
|
||||
| {% endif %}
|
||||
| {% endwith %}
|
||||
|
||||
nav.navbar
|
||||
.navbar-container
|
||||
header.navbar-header
|
||||
button.navbar-toggle(data-target=".navbar-collapse", data-toggle="collapse", type="button")
|
||||
span.sr-only Toggle navigation
|
||||
i.pi-menu
|
||||
nav.navbar.navbar-expand-md.fixed-top.bg-white
|
||||
a.navbar-brand(
|
||||
href="{{ url_for('main.homepage') }}",
|
||||
title="Blender Cloud")
|
||||
span.app-logo
|
||||
i.pi-blender-cloud
|
||||
|
||||
button.navbar-toggler.text-light(
|
||||
data-target=".navbar-collapse",
|
||||
data-toggle="collapse",
|
||||
type="button")
|
||||
span.sr-only Toggle navigation
|
||||
span.navbar-toggler-icon.d-flex.align-items-center
|
||||
i.pi-menu
|
||||
|
||||
| {% block navigation_tabs %}
|
||||
| {% endblock navigation_tabs %}
|
||||
|
||||
| {% block navigation_search %}
|
||||
// TODO (pablo) - bring it back asap
|
||||
.search-input
|
||||
input#cloud-search(
|
||||
type="text",
|
||||
@@ -99,8 +98,8 @@ html(lang="en")
|
||||
i.search-icon.pi-search
|
||||
| {% endblock navigation_search %}
|
||||
|
||||
nav.collapse.navbar-collapse
|
||||
ul.nav.navbar-nav.navbar-right
|
||||
.collapse.navbar-collapse
|
||||
ul.navbar-nav.ml-auto
|
||||
| {% if node and node.properties and node.properties.category %}
|
||||
| {% set category = node.properties.category %}
|
||||
| {% else %}
|
||||
@@ -117,7 +116,7 @@ html(lang="en")
|
||||
class="{% if category == 'blog' %}active{% endif %}")
|
||||
span Blog
|
||||
|
||||
li(class="dropdown libraries")
|
||||
li.dropdown
|
||||
a.navbar-item.dropdown-toggle(
|
||||
href="",
|
||||
data-toggle="dropdown",
|
||||
@@ -125,7 +124,7 @@ html(lang="en")
|
||||
span Libraries
|
||||
i.pi-angle-down
|
||||
|
||||
ul.dropdown-menu
|
||||
ul.dropdown-menu.p-0
|
||||
li
|
||||
a.navbar-item(
|
||||
href="{{ url_for('projects.view', project_url='hdri') }}",
|
||||
@@ -152,9 +151,9 @@ html(lang="en")
|
||||
| Characters
|
||||
|
||||
|
||||
li(class="dropdown libraries")
|
||||
li(class="dropdown")
|
||||
a.navbar-item.dropdown-toggle(
|
||||
href="",
|
||||
href="{{ url_for('cloud.workshops') }}"
|
||||
data-toggle="dropdown",
|
||||
title="Training")
|
||||
span Training
|
||||
@@ -186,41 +185,99 @@ html(lang="en")
|
||||
i.pi-image
|
||||
| Art Gallery
|
||||
|
||||
li
|
||||
a.navbar-item(
|
||||
li(class="dropdown")
|
||||
a.navbar-item.dropdown-toggle(
|
||||
href="{{ url_for('cloud.open_projects') }}",
|
||||
title="Browse all the Open Projects",
|
||||
data-toggle="tooltip",
|
||||
data-placement="bottom",
|
||||
data-toggle="dropdown",
|
||||
class="{% if category in ['open-projects', 'film'] %}active{% endif %}")
|
||||
span Open Projects
|
||||
i.pi-angle-down
|
||||
|
||||
ul.dropdown-menu
|
||||
li
|
||||
a.navbar-item(href="/p/spring")
|
||||
span.px-2 Spring
|
||||
li
|
||||
a.navbar-item(href="/p/hero")
|
||||
span.px-2 Hero
|
||||
li
|
||||
a.navbar-item(href="/p/dailydweebs")
|
||||
span.px-2 The Daily Dweebs
|
||||
li
|
||||
a.navbar-item(href="/p/agent-327")
|
||||
span.px-2 Agent 327
|
||||
li
|
||||
a.navbar-item(href="/p/caminandes-3")
|
||||
span.px-2 Caminandes: Llamigos
|
||||
|
||||
li.dropdown-divider
|
||||
|
||||
li
|
||||
a.navbar-item(href="{{ url_for('cloud.open_projects') }}")
|
||||
span.pl-2 All Open Projects
|
||||
|
||||
li(class="dropdown")
|
||||
a.navbar-item.dropdown-toggle(
|
||||
href="{{ url_for('cloud.services') }}",
|
||||
title="Blender Cloud Services",
|
||||
data-toggle="dropdown",
|
||||
class="{% if category == 'services' %}active{% endif %}")
|
||||
span Services
|
||||
i.pi-angle-down
|
||||
|
||||
ul.dropdown-menu
|
||||
li
|
||||
a.navbar-item(
|
||||
href="/attract",
|
||||
title="Production Management",
|
||||
data-toggle="tooltip",
|
||||
data-placement="left")
|
||||
i.pi-attract
|
||||
| Attract
|
||||
li
|
||||
a.navbar-item(
|
||||
href="/flamenco",
|
||||
title="Render Management",
|
||||
data-toggle="tooltip",
|
||||
data-placement="left")
|
||||
i.pi-flamenco
|
||||
| Flamenco
|
||||
|
||||
li
|
||||
a.navbar-item(
|
||||
href="/services#blender-cloud-add-on",
|
||||
title="Blender Sync, Texture Browser and more",
|
||||
data-toggle="tooltip",
|
||||
data-placement="left")
|
||||
i.pi-blender
|
||||
| Blender Cloud Add-on
|
||||
|
||||
li.dropdown-divider
|
||||
|
||||
li
|
||||
a.navbar-item(
|
||||
href="{{ url_for('cloud.services') }}",
|
||||
title="Blender Cloud Services",
|
||||
title="All Blender Cloud services",
|
||||
data-toggle="tooltip",
|
||||
data-placement="bottom",
|
||||
class="{% if category == 'services' %}active{% endif %}")
|
||||
span Services
|
||||
data-placement="left")
|
||||
i.pi-list
|
||||
| All Services
|
||||
|
||||
| {% endblock navigation_sections %}
|
||||
|
||||
| {% block navigation_user %}
|
||||
| {% include 'menus/notifications.html' %}
|
||||
| {% include 'menus/user.html' %}
|
||||
| {% endblock navigation_user %}
|
||||
|
||||
| {% if current_user.is_anonymous %}
|
||||
li
|
||||
a.navbar-item(
|
||||
li.pt-1
|
||||
a.btn.btn-sm.btn-primary.px-3.mx-1(
|
||||
href="https://store.blender.org/product/membership/",
|
||||
title="Sign up") Sign up
|
||||
| {% endif %}
|
||||
|
||||
|
||||
| {% block navigation_user %}
|
||||
|
||||
| {% include 'menus/notifications.html' %}
|
||||
| {% include 'menus/user.html' %}
|
||||
|
||||
| {% endblock navigation_user %}
|
||||
|
||||
|
||||
.page-content
|
||||
#search-overlay
|
||||
| {% block page_overlay %}
|
||||
@@ -230,46 +287,101 @@ html(lang="en")
|
||||
| {% block body %}{% endblock %}
|
||||
|
||||
| {% block footer_container %}
|
||||
#footer-container
|
||||
.footer-wrapper
|
||||
| {% block footer_navigation %}
|
||||
#footer-navigation
|
||||
.footer-navigation
|
||||
.container
|
||||
.row
|
||||
.col-md-4.col-xs-6
|
||||
.footer-support
|
||||
h4 Support & Feedback
|
||||
p.
|
||||
Let us know what you think or if you have any issues
|
||||
just write to cloudsupport at blender dot org
|
||||
h4
|
||||
a(href="{{ url_for('main.homepage') }}")
|
||||
i.pi-blender-cloud-logo
|
||||
|
||||
.col-md-2.col-xs-6
|
||||
ul.footer-social
|
||||
li
|
||||
a(href="https://www.facebook.com/BlenderCloudOfficial/",
|
||||
title="Follow us on Facebook")
|
||||
i.pi-social-facebook
|
||||
li
|
||||
a(href="https://twitter.com/Blender_Cloud",
|
||||
p.pl-2.
|
||||
Blender Cloud is the creative hub for your projects,
|
||||
powered by Free and Open Source Software.
|
||||
|
||||
h5.d-flex
|
||||
a.px-2(href="https://twitter.com/Blender_Cloud",
|
||||
title="Follow us on Twitter")
|
||||
i.pi-social-youtube
|
||||
|
||||
a.px-2(href="https://twitter.com/Blender_Cloud",
|
||||
title="Follow us on Twitter")
|
||||
i.pi-social-twitter
|
||||
|
||||
a.px-2(href="https://www.facebook.com/BlenderCloudOfficial/",
|
||||
title="Follow us on Facebook")
|
||||
i.pi-social-facebook
|
||||
|
||||
.col-md-2.col-xs-6
|
||||
h4
|
||||
a(href="{{ url_for('main.homepage') }}")
|
||||
| Blender Cloud
|
||||
ul.footer-links
|
||||
h7.font-weight-bold
|
||||
| TRAINING
|
||||
|
||||
ul.list-unstyled
|
||||
li
|
||||
a(href="{{ url_for('cloud.courses') }}")
|
||||
| Courses
|
||||
li
|
||||
a(href="{{ url_for('cloud.workshops') }}")
|
||||
| Workshops
|
||||
li
|
||||
a(href="{{ url_for('projects.view', project_url='gallery') }}")
|
||||
| Art Gallery
|
||||
|
||||
.col-md-2.col-xs-6
|
||||
h7.font-weight-bold
|
||||
| LIBRARIES
|
||||
|
||||
ul.list-unstyled
|
||||
li
|
||||
a(href="{{ url_for('main.main_blog') }}",
|
||||
title="Blender Cloud Blog")
|
||||
| Blog
|
||||
| HDRIs
|
||||
li
|
||||
a(href="{{ url_for('cloud.services') }}",
|
||||
title="Blender Cloud Services")
|
||||
| Services
|
||||
| Textures
|
||||
li
|
||||
a(href="{{ url_for('cloud.about') }}",
|
||||
title="About Blender Cloud")
|
||||
| About
|
||||
| Characters
|
||||
|
||||
.col-md-2.col-xs-6
|
||||
h7.font-weight-bold
|
||||
a(href="{{ url_for('cloud.services') }}")
|
||||
| SERVICES
|
||||
|
||||
ul.list-unstyled
|
||||
li
|
||||
a(href="{{ url_for('main.main_blog') }}",
|
||||
title="Blender Cloud Blog")
|
||||
| Add-on
|
||||
li
|
||||
a(href="{{ url_for('main.main_blog') }}",
|
||||
title="Blender Cloud Blog")
|
||||
| Blender Sync
|
||||
li
|
||||
a(href="{{ url_for('cloud.services') }}",
|
||||
title="Blender Cloud Services")
|
||||
| Attract
|
||||
li
|
||||
a(href="{{ url_for('cloud.about') }}",
|
||||
title="About Blender Cloud")
|
||||
| Flamenco
|
||||
li
|
||||
a(href="{{ url_for('cloud.about') }}",
|
||||
title="About Blender Cloud")
|
||||
| Image Sharing
|
||||
|
||||
.col-md-2.col-xs-6
|
||||
h7.font-weight-bold
|
||||
| BLENDER
|
||||
ul.list-unstyled
|
||||
li
|
||||
a(href="{{ url_for('main.main_blog') }}",
|
||||
title="Blender Cloud Blog")
|
||||
| blender.org
|
||||
li
|
||||
a(href="{{ url_for('cloud.terms_and_conditions') }}",
|
||||
title="Terms and Conditions")
|
||||
@@ -278,33 +390,11 @@ html(lang="en")
|
||||
a(href="{{ url_for('cloud.privacy') }}",
|
||||
title="Privacy")
|
||||
| Privacy
|
||||
|
||||
.col-md-2.col-xs-6
|
||||
h4
|
||||
a(href="https://www.blender.org",
|
||||
title="Blender official Website")
|
||||
| Blender
|
||||
ul.footer-links
|
||||
li
|
||||
a(href="https://www.blender.org",
|
||||
title="Blender official Website")
|
||||
| Blender.org
|
||||
li
|
||||
a(href="https://store.blender.org/",
|
||||
title="The official Blender Store")
|
||||
| Blender Store
|
||||
|
||||
.col-md-2.col-xs-6.special
|
||||
| With the support of the <br/> MEDIA Programme of the European Union<br/><br/>
|
||||
img(alt="MEDIA Programme of the European Union",
|
||||
src="https://gooseberry.blender.org/wp-content/uploads/2014/01/media_programme.png")
|
||||
| {% endblock footer_navigation %}
|
||||
|
||||
| {% block footer %}
|
||||
footer.container
|
||||
#hop(title="Be awesome in space")
|
||||
i.pi-angle-up
|
||||
| {% endblock footer %}
|
||||
|
||||
| {% endblock footer_container %}
|
||||
|
||||
#notification-pop(data-url="", data-read-toggle="")
|
||||
@@ -317,10 +407,21 @@ html(lang="en")
|
||||
span.nc-date
|
||||
a(href="")
|
||||
|
||||
noscript
|
||||
link(href='//fonts.googleapis.com/css?family=Roboto:300,400', rel='stylesheet', type='text/css')
|
||||
script(src="{{ url_for('static_cloud', filename='assets/js/bootstrap.min.js') }}")
|
||||
|
||||
script(src="{{ url_for('static_pillar', filename='assets/js/vendor/jquery.bootstrap-3.3.7.min.js', v=9112017) }}")
|
||||
| {% if current_user.is_authenticated %}
|
||||
script(src="{{ url_for('static_pillar', filename='assets/js/vendor/jquery.typewatch-3.0.0.min.js') }}")
|
||||
script.
|
||||
// When sending an AJAX request, always add the X-CSRFToken header to it.
|
||||
var csrf_token = "{{ csrf_token() }}";
|
||||
$.ajaxSetup({
|
||||
beforeSend: function (xhr, settings) {
|
||||
if (!/^(GET|HEAD|OPTIONS|TRACE)$/i.test(settings.type) && !this.crossDomain) {
|
||||
xhr.setRequestHeader("X-CSRFToken", csrf_token);
|
||||
}
|
||||
}
|
||||
});
|
||||
| {% endif %}
|
||||
|
||||
script.
|
||||
$(document).ready(function() {
|
||||
|
69
src/templates/mixins/components.pug
Normal file
@@ -0,0 +1,69 @@
|
||||
// {#
|
||||
// Header of landing pages. title or text can be skipped:
|
||||
// +jumbotron("{{ page_title }}", null, "{{ page_header_image }}")
|
||||
// Any extra attributes added (in a separate group) will be passed as is:
|
||||
// +jumbotron("{{ page_title }}", null, "{{ page_header_image }}")(data-node-id='{{ node._id }}')
|
||||
// #}
|
||||
mixin jumbotron(title, text, image, url)
|
||||
if url
|
||||
a.jumbotron.jumbotron-overlay.text-white(
|
||||
style='background-image: url(' + image + ');',
|
||||
href=url)&attributes(attributes)
|
||||
.container
|
||||
.row
|
||||
.col-md-9
|
||||
if title
|
||||
.display-4.text-uppercase.font-weight-bold
|
||||
=title
|
||||
if text
|
||||
.lead
|
||||
=text
|
||||
else
|
||||
.jumbotron.jumbotron-overlay.text-white(style='background-image: url(' + image + ');')&attributes(attributes)
|
||||
.container
|
||||
.row
|
||||
.col-md-9
|
||||
if title
|
||||
.display-4.text-uppercase.font-weight-bold
|
||||
=title
|
||||
if text
|
||||
.lead
|
||||
=text
|
||||
|
||||
// {# Secondary navigation.
|
||||
// e.g. Workshops, Courses. #}
|
||||
mixin nav-secondary(title)
|
||||
ul.nav.nav-secondary&attributes(attributes)
|
||||
if title
|
||||
li.font-weight-bold.px-2
|
||||
=title
|
||||
|
||||
if block
|
||||
block
|
||||
else
|
||||
p No items defined.
|
||||
|
||||
mixin nav-secondary-link()
|
||||
li.nav-item
|
||||
a.nav-link&attributes(attributes)
|
||||
block
|
||||
|
||||
// {# Takes as argument the number of columns to use in this deck. 1-6 #}
|
||||
mixin card-deck(columns)
|
||||
.card-deck.card-padless(class='card-' + columns + '-columns')
|
||||
if block
|
||||
block
|
||||
else
|
||||
p No cards defined.
|
||||
|
||||
// {#
|
||||
// Passes all attributes to the card.
|
||||
// You can do fun stuff in a loop even like:
|
||||
// +card(data-url="{{ url_for('projects.view', project_url=project.url) }}", tabindex='{{ loop.index }}')
|
||||
// #}
|
||||
mixin card()
|
||||
.card.card-fade.cursor-pointer.mb-4.js-project-go&attributes(attributes)
|
||||
if block
|
||||
block
|
||||
else
|
||||
p No card content defined.
|
164
src/templates/nodes/custom/blog/_macros.pug
Normal file
@@ -0,0 +1,164 @@
|
||||
include ../../../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 }}",
|
||||
"{{ node.picture.thumbnail('h', api=api) }}",
|
||||
"{{ node.url }}")(class="row")
|
||||
| {% else %}
|
||||
.pt-3.text-center.text-muted
|
||||
h2
|
||||
a.text-muted(href="{{ node.url }}")
|
||||
| {{ node.name }}
|
||||
ul.d-flex.list-unstyled.justify-content-center
|
||||
| {% if node.project.name %}
|
||||
li.pr-2 {{ node.project.name }}
|
||||
| {% endif %}
|
||||
| {% if node.user.full_name %}
|
||||
li.pr-2
|
||||
| {{ node.user.full_name }}
|
||||
| {% endif %}
|
||||
li
|
||||
a.px-2.text-muted(href="{{ node.url }}",
|
||||
title="Updated {{ node._updated | pretty_date }}")
|
||||
| {{ node._created | pretty_date }}
|
||||
li
|
||||
a.px-2(href="{{ node.url }}#comments")
|
||||
| Leave a comment
|
||||
|
||||
| {% if node.has_method('PUT') %}
|
||||
li
|
||||
a.px-2(href="{{url_for('nodes.edit', node_id=node._id)}}")
|
||||
i.pi-edit
|
||||
| Edit Post
|
||||
| {% endif %}
|
||||
| {% endif %}
|
||||
|
||||
| {% if project and project._id != config.MAIN_PROJECT_ID %}
|
||||
| {{ projectmacros.render_secondary_navigation(project, pages=pages) }}
|
||||
| {% endif %}
|
||||
|
||||
.row
|
||||
.col-md-9.mx-auto
|
||||
|
||||
.item-content.pt-4
|
||||
| {{ node.properties | markdowned('content') }}
|
||||
|
||||
hr.my-4
|
||||
| {% endmacro %}
|
||||
|
||||
//- ******************************************************* -//
|
||||
| {% macro render_blog_list_item(node) %}
|
||||
.row.position-relative.py-2
|
||||
.col-md-1
|
||||
| {% if node.picture %}
|
||||
a.imgs-fluid(href="{{ node.url }}")
|
||||
img(src="{{ node.picture.thumbnail('s', api=api) }}")
|
||||
| {% else %}
|
||||
.bg-primary.rounded.h-100
|
||||
a.d-flex.align-items-center.justify-content-center.h-100.text-white(href="{{ node.url }}")
|
||||
i.pi-document-text
|
||||
| {% endif %}
|
||||
|
||||
.col-md-11
|
||||
h5
|
||||
a.text-muted(href="{{ node.url }}") {{node.name}}
|
||||
|
||||
.text-muted.
|
||||
#[span(title="{{node._created}}") {{node._created | pretty_date }}]
|
||||
{% if node._created != node._updated %}
|
||||
#[span(title="{{node._updated}}") (updated {{node._updated | pretty_date }})]
|
||||
{% endif %}
|
||||
{% if node.properties.category %} · {{node.properties.category}}{% endif %}
|
||||
· {{node.user.full_name}}
|
||||
{% if node.properties.status != 'published' %} · {{ node.properties.status}} {% endif %}
|
||||
|
||||
| {% endmacro %}
|
||||
|
||||
|
||||
//- ******************************************************* -//
|
||||
| {% macro render_blog_index(project, posts, can_create_blog_posts, api, more_posts_available, posts_meta, pages=None) %}
|
||||
| {% if can_create_blog_posts %}
|
||||
+nav-secondary
|
||||
+nav-secondary-link(href="{{url_for('nodes.posts_create', project_id=project._id)}}")
|
||||
span.text-success
|
||||
i.pi-plus
|
||||
| Create New Blog Post
|
||||
| {% endif %}
|
||||
|
||||
| {% if posts %}
|
||||
| {{ render_blog_post(posts[0], project=project, pages=pages) }}
|
||||
|
||||
.container
|
||||
.row
|
||||
.col-md-9.mx-auto
|
||||
| {% for node in posts[1:] %}
|
||||
| {% if loop.first %}
|
||||
h5.text-muted.text-center Blasts from the past
|
||||
| {% endif %}
|
||||
| {{ render_blog_list_item(node) }}
|
||||
| {% endfor %}
|
||||
|
||||
| {% if more_posts_available %}
|
||||
.blog-archive-navigation
|
||||
a(href="{{ project.blog_archive_url }}")
|
||||
| {{posts_meta.total - posts|length}} more blog posts over here
|
||||
i.pi-angle-right
|
||||
| {% endif %}
|
||||
|
||||
| {% else %}
|
||||
|
||||
.text-center
|
||||
p No posts... yet!
|
||||
|
||||
| {% endif %} {# posts #}
|
||||
| {% endmacro %}
|
||||
|
||||
|
||||
//- Macro for rendering the navigation buttons for prev/next pages -//
|
||||
| {% macro render_archive_pagination(project) %}
|
||||
.blog-archive-navigation
|
||||
| {% if project.blog_archive_prev %}
|
||||
a.archive-nav-button(
|
||||
href="{{ project.blog_archive_prev }}", rel="prev")
|
||||
i.pi-angle-left
|
||||
| Previous page
|
||||
| {% else %}
|
||||
span.archive-nav-button
|
||||
i.pi-angle-left
|
||||
| Previous page
|
||||
| {% endif %}
|
||||
|
||||
a.archive-nav-button(
|
||||
href="{{ url_for('main.project_blog', project_url=project.url) }}")
|
||||
| Blog Index
|
||||
|
||||
| {% if project.blog_archive_next %}
|
||||
a.archive-nav-button(
|
||||
href="{{ project.blog_archive_next }}", rel="next")
|
||||
| Next page
|
||||
i.pi-angle-right
|
||||
| {% else %}
|
||||
span.archive-nav-button
|
||||
| Next page
|
||||
i.pi-angle-right
|
||||
| {% endif %}
|
||||
|
||||
| {% endmacro %}
|
||||
|
||||
| {% macro render_archive(project, posts, posts_meta) %}
|
||||
|
||||
| {{ render_archive_pagination(project) }}
|
||||
|
||||
| {% for node in posts %}
|
||||
| {{ render_blog_list_item(node) }}
|
||||
| {% endfor %}
|
||||
|
||||
| {{ render_archive_pagination(project) }}
|
||||
|
||||
| {% endmacro %}
|
213
src/templates/organizations/index.pug
Normal file
@@ -0,0 +1,213 @@
|
||||
| {% extends 'layout.html' %}
|
||||
| {% from '_macros/_navigation.html' import navigation_tabs %}
|
||||
include ../mixins/components
|
||||
|
||||
| {% set title = 'organizations' %}
|
||||
| {% block page_title %}Organizations{% endblock %}
|
||||
|
||||
| {% block og %}
|
||||
meta(property="og:title", content="Dashboard")
|
||||
meta(name="twitter:title", content="Blender Cloud")
|
||||
|
||||
meta(property="og:url", content="https://cloud.blender.org/{{ request.path }}")
|
||||
meta(property="og:type", content="website")
|
||||
|
||||
meta(property="og:image", content="{{ url_for('static', filename='assets/img/backgrounds/cloud_services_oti.jpg')}}")
|
||||
meta(name="twitter:image", content="{{ url_for('static', filename='assets/img/backgrounds/cloud_services_oti.jpg')}}")
|
||||
| {% endblock %}
|
||||
|
||||
|
||||
| {% block navigation_tabs %}
|
||||
| {{ navigation_tabs(title) }}
|
||||
| {% endblock navigation_tabs %}
|
||||
|
||||
| {% block body %}
|
||||
+nav-secondary
|
||||
| {% if can_create_organization %}
|
||||
+nav-secondary-link(
|
||||
class="create",
|
||||
onclick='createNewOrganization(this)')
|
||||
span.text-success
|
||||
i.pi-plus
|
||||
| Create Organization
|
||||
| {% endif %}
|
||||
|
||||
li#create_organization_result_panel.result
|
||||
|
||||
.container-fluid.dashboard-container
|
||||
.row
|
||||
.col-md-6
|
||||
ul.projects__list
|
||||
| {% if organizations %}
|
||||
| {% for organization in organizations['_items'] %}
|
||||
| {% set link_url = url_for('pillar.web.organizations.view_embed', organization_id=organization._id) %}
|
||||
li.projects__list-item(
|
||||
data-url="{{ link_url }}",
|
||||
id="organization-{{ organization._id }}")
|
||||
a.projects__list-thumbnail(
|
||||
href="{{ link_url }}")
|
||||
i.pi-users
|
||||
.projects__list-details
|
||||
a.title(href="{{ link_url }}")
|
||||
| {{ organization.name }}
|
||||
|
||||
ul.meta
|
||||
li(title="Members")
|
||||
| {{ organization.members|hide_none|count }} Member{{ organization.members|hide_none|count|pluralize }}
|
||||
| {% if (organization.unknown_members|count) != 0 %}
|
||||
| ({{ organization.unknown_members|hide_none|count }} pending)
|
||||
| {% endif %}
|
||||
li(title="Seats")
|
||||
| {{ organization.seat_count }} Seat{{ organization.seat_count|pluralize }}
|
||||
|
||||
| {% endfor %}
|
||||
| {% else %}
|
||||
li.projects__list-item
|
||||
a.projects__list-thumbnail
|
||||
i.pi-blender-cloud
|
||||
.projects__list-details
|
||||
span Create an Organization to get started!
|
||||
| {% endif %}
|
||||
|
||||
.col-md-6.py-1.pb-3
|
||||
#item-details
|
||||
|
||||
| {% endblock %}
|
||||
|
||||
|
||||
| {% block footer_scripts %}
|
||||
script(src="{{ url_for('static_pillar', filename='assets/js/vendor/jquery.typeahead-0.11.1.min.js')}}")
|
||||
script(src="{{ url_for('static_pillar', filename='assets/js/vendor/jquery.autocomplete-0.22.0.min.js') }}", async=true)
|
||||
|
||||
script.
|
||||
|
||||
/* Returns a more-or-less reasonable message given an error response object. */
|
||||
function xhrErrorResponseMessage(err) {
|
||||
if (typeof err.responseJSON == 'undefined')
|
||||
return err.statusText;
|
||||
|
||||
if (typeof err.responseJSON._error != 'undefined' && typeof err.responseJSON._error.message != 'undefined')
|
||||
return err.responseJSON._error.message;
|
||||
|
||||
if (typeof err.responseJSON._message != 'undefined')
|
||||
return err.responseJSON._message
|
||||
|
||||
return err.statusText;
|
||||
}
|
||||
|
||||
/**
|
||||
* Open an organization in the #item-details div.
|
||||
*/
|
||||
function item_open(item_id, pushState)
|
||||
{
|
||||
if (item_id === undefined ) {
|
||||
throw new ReferenceError("item_open(" + item_id + ") called.");
|
||||
}
|
||||
|
||||
// Style elements starting with item_type and dash, e.g. "#job-uuid"
|
||||
var clean_classes = 'active processing';
|
||||
var current_item = $('#organization-' + item_id);
|
||||
|
||||
$('[id^="organization-"]').removeClass(clean_classes);
|
||||
current_item
|
||||
.removeClass(clean_classes)
|
||||
.addClass('processing');
|
||||
|
||||
var item_url = '/o/' + item_id;
|
||||
|
||||
$.get(item_url, function(item_data) {
|
||||
$('#item-details').html(item_data);
|
||||
|
||||
current_item
|
||||
.removeClass(clean_classes)
|
||||
.addClass('active');
|
||||
|
||||
}).fail(function(xhr) {
|
||||
if (console) {
|
||||
console.log('Error fetching organization', item_id, 'from', item_url);
|
||||
console.log('XHR:', xhr);
|
||||
}
|
||||
|
||||
current_item.removeClass(clean_classes);
|
||||
toastr.error('Failed to open organization');
|
||||
|
||||
if (xhr.status) {
|
||||
$('#item-details').html(xhr.responseText);
|
||||
} else {
|
||||
$('#item-details').html('<p class="text-danger">Opening ' + item_type + ' failed. There possibly was ' +
|
||||
'an error connecting to the server. Please check your network connection and ' +
|
||||
'try again.</p>');
|
||||
}
|
||||
});
|
||||
|
||||
// Determine whether we should push the new state or not.
|
||||
pushState = (typeof pushState !== 'undefined') ? pushState : true;
|
||||
if (!pushState) return;
|
||||
|
||||
// Push the correct URL onto the history.
|
||||
var push_state = {itemId: item_id};
|
||||
|
||||
window.history.pushState(
|
||||
push_state,
|
||||
'Organization: ' + item_id,
|
||||
item_url
|
||||
);
|
||||
}
|
||||
|
||||
$('li.projects__list-item').click(function(e){
|
||||
url = $(this).data('url');
|
||||
if (typeof url === 'undefined') return;
|
||||
|
||||
window.location.href = url;
|
||||
if (console) console.log(url);
|
||||
|
||||
$(this).addClass('active');
|
||||
$(this).find('.projects__list-thumbnail i')
|
||||
.removeAttr('class')
|
||||
.addClass('pi-spin spin');
|
||||
});
|
||||
|
||||
|
||||
{% if open_organization_id %}
|
||||
$(function() { item_open('{{ open_organization_id }}', false); });
|
||||
{% endif %}
|
||||
|
||||
{% if can_create_organization %}
|
||||
function createNewOrganization(button) {
|
||||
$(button)
|
||||
.attr('disabled', 'disabled')
|
||||
.fadeTo(200, 0.1);
|
||||
$('#create_organization_result_panel').html('');
|
||||
|
||||
// TODO: create a form to get the initial info from the user.
|
||||
$.post(
|
||||
'{{ url_for('pillar.web.organizations.create_new') }}',
|
||||
{
|
||||
name: 'New Organization',
|
||||
seat_count: 1,
|
||||
}
|
||||
)
|
||||
.done(function(result) {
|
||||
var $p = $('<p>').text('organization created, reloading list.')
|
||||
$('#create_organization_result_panel').html($p);
|
||||
|
||||
window.location.href = result.location;
|
||||
})
|
||||
.fail(function(err) {
|
||||
var msg = xhrErrorResponseMessage(err);
|
||||
$('#create_organization_result_panel').html('Error creating organization: ' + msg);
|
||||
|
||||
$(button)
|
||||
.fadeTo(1000, 1.0)
|
||||
.queue(function() {
|
||||
$(this)
|
||||
.removeAttr('disabled')
|
||||
.dequeue()
|
||||
;
|
||||
})
|
||||
})
|
||||
;
|
||||
return false;
|
||||
}
|
||||
{% endif %}
|
||||
| {% endblock %}
|
59
src/templates/projects/home_index.pug
Normal file
@@ -0,0 +1,59 @@
|
||||
| {% extends 'projects/home_layout.html' %}
|
||||
| {% set subtab = 'blender_sync' %}
|
||||
| {% set learn_more_btn_url = '/blog/introducing-blender-sync' %}
|
||||
| {% block currenttab %}
|
||||
.container-fluid
|
||||
section.nav-tabs__tab.active#tab-blender_sync
|
||||
.tab_header-container
|
||||
.tab_header-intro(
|
||||
style="background-image: url({{ url_for('static', filename='assets/img/backgrounds/pattern_01.jpg')}})")
|
||||
.tab_header-intro_text
|
||||
h2 Connect Blender with the Cloud
|
||||
p
|
||||
| Save your Blender preferences and keymaps once, load them anywhere.
|
||||
<br/>
|
||||
| Use the
|
||||
=' '
|
||||
a(href='https://cloud.blender.org/r/downloads/blender_cloud-latest-bundle.zip') Blender Cloud add-on
|
||||
=' '
|
||||
| to synchronise your settings from within Blender.
|
||||
|
||||
| {% if show_addon_download_buttons %}
|
||||
.row
|
||||
.col-md-6
|
||||
a.btn.btn-block.btn-outline-success(
|
||||
href="https://cloud.blender.org/r/downloads/blender_cloud-latest-bundle.zip")
|
||||
i.pi-download
|
||||
| Download <small>v</small>{{ config.BLENDER_CLOUD_ADDON_VERSION }}
|
||||
.col-md-6
|
||||
a.btn.btn-link(
|
||||
href="{{ learn_more_btn_url }}")
|
||||
| Learn More
|
||||
i.pi-angle-right
|
||||
| {% endif %}
|
||||
|
||||
.tab_header-intro_icons
|
||||
i.pi-blender
|
||||
i.pi-heart-filled
|
||||
i.pi-blender-cloud
|
||||
|
||||
| {% for version in synced_versions %}
|
||||
.blender_sync-main
|
||||
.blender_sync-main-header
|
||||
h5.blender_sync-main-title
|
||||
i.pi-blender
|
||||
| Blender {{ version.version }}
|
||||
.blender_sync-main-last
|
||||
| Last synced on: {{ version.date|pretty_date }}
|
||||
| {% else %}
|
||||
.blender_sync-main.empty
|
||||
.blender_sync-main-header
|
||||
span.blender_sync-main-title
|
||||
| No settings synced yet
|
||||
<hr/>
|
||||
a.download(
|
||||
href='https://cloud.blender.org/r/downloads/blender_cloud-latest-bundle.zip')
|
||||
| Download add-on
|
||||
| {% endfor %}
|
||||
| {% endblock %}
|
||||
|
51
src/templates/projects/home_layout.pug
Normal file
@@ -0,0 +1,51 @@
|
||||
| {% extends 'layout.html' %}
|
||||
| {% from '_macros/_navigation.html' import navigation_tabs %}
|
||||
include ../mixins/components
|
||||
|
||||
| {% set title = 'home' %}
|
||||
|
||||
| {% block og %}
|
||||
meta(property="og:type", content="website")
|
||||
meta(property="og:url", content="https://cloud.blender.org{{ request.path }}")
|
||||
|
||||
meta(property="og:title", content="Blender Cloud - Home")
|
||||
meta(name="twitter:title", content="Blender Cloud")
|
||||
|
||||
meta(property="og:image", content="{{ url_for('static', filename='assets/img/backgrounds/cloud_services_oti.jpg')}}")
|
||||
meta(name="twitter:image", content="{{ url_for('static', filename='assets/img/backgrounds/cloud_services_oti.jpg')}}")
|
||||
| {% endblock %}
|
||||
|
||||
| {% block page_title %}
|
||||
| {{current_user.full_name}}
|
||||
| {% endblock %}
|
||||
|
||||
| {% block navigation_tabs %}
|
||||
| {{ navigation_tabs(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')}}")
|
||||
| Blender Sync
|
||||
|
||||
+nav-secondary-link(id="subtab-images", data-tab-url="{{ url_for('projects.home_project_shared_images')}}")
|
||||
| Images
|
||||
|
||||
| {% block currenttab %}{% endblock %}
|
||||
| {% endblock %}
|
||||
|
||||
| {% block footer_scripts %}
|
||||
script.
|
||||
$(document).ready(function () {
|
||||
$('#subtab-{{ subtab }}').addClass('active');
|
||||
|
||||
var $nav_tabs = $('#sub-nav-tabs__list').find('a.nav-link');
|
||||
$nav_tabs.on('click', function (e) {
|
||||
console.log($(this));
|
||||
window.location = $(this).attr('data-tab-url');
|
||||
});
|
||||
});
|
||||
| {% endblock %}
|
300
src/templates/projects/index_dashboard.pug
Normal file
@@ -0,0 +1,300 @@
|
||||
| {% extends 'layout.html' %}
|
||||
| {% from '_macros/_navigation.html' import navigation_tabs %}
|
||||
include ../mixins/components
|
||||
|
||||
| {% set title = 'dashboard' %}
|
||||
|
||||
| {% block og %}
|
||||
meta(property="og:title", content="Dashboard")
|
||||
meta(name="twitter:title", content="Blender Cloud")
|
||||
|
||||
meta(property="og:url", content="https://cloud.blender.org/{{ request.path }}")
|
||||
meta(property="og:type", content="website")
|
||||
|
||||
meta(property="og:image", content="{{ url_for('static', filename='assets/img/backgrounds/cloud_services_oti.jpg')}}")
|
||||
meta(name="twitter:image", content="{{ url_for('static', filename='assets/img/backgrounds/cloud_services_oti.jpg')}}")
|
||||
| {% endblock %}
|
||||
|
||||
| {% block page_title %}
|
||||
| {{current_user.full_name}}
|
||||
| {% endblock %}
|
||||
|
||||
| {% block css %}
|
||||
| {{ super() }}
|
||||
style.
|
||||
.deleted-projects-toggle {
|
||||
z-index: 10;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
font-size: 20px;
|
||||
padding: 3px;
|
||||
text-shadow: 0 0 2px white;
|
||||
}
|
||||
.deleted-projects-toggle .show-deleted {
|
||||
color: #aaa;
|
||||
}
|
||||
.deleted-projects-toggle .hide-deleted {
|
||||
color: #bbb;
|
||||
}
|
||||
| {% endblock %}
|
||||
|
||||
| {% block navigation_tabs %}
|
||||
| {{ navigation_tabs(title) }}
|
||||
| {% endblock navigation_tabs %}
|
||||
|
||||
|
||||
| {% block body %}
|
||||
.dashboard-container
|
||||
section.dashboard-main
|
||||
section#projects.bg-white
|
||||
+nav-secondary()(id='sub-nav-tabs__list')
|
||||
+nav-secondary-link(data-tab-toggle='own_projects', class="active")
|
||||
| Own Projects
|
||||
| {% if projects_user|length != 0 %}
|
||||
span ({{ projects_user|length }})
|
||||
| {% endif %}
|
||||
|
||||
+nav-secondary-link(data-tab-toggle='shared')
|
||||
| Shared with me
|
||||
| {% if projects_shared|length != 0 %}
|
||||
span ({{ projects_shared|length }})
|
||||
| {% endif %}
|
||||
|
||||
| {% if current_user.has_cap('subscriber') %}
|
||||
+nav-secondary-link(
|
||||
id="project-create",
|
||||
data-url="{{ url_for('projects.create') }}",
|
||||
href="{{ url_for('projects.create') }}")
|
||||
span.text-success
|
||||
| #[i.pi-plus] Create New Project
|
||||
| {% elif current_user.has_cap('can-renew-subscription') %}
|
||||
+nav-secondary-link(
|
||||
id="project-create",
|
||||
data-url="{{ url_for('projects.create') }}",
|
||||
href="/renew",
|
||||
target="_blank")
|
||||
| #[i.pi-heart-filled.text-danger] Resubscribe to Create a Project
|
||||
| {% endif %}
|
||||
|
||||
nav.nav-tabs__tab.active#own_projects
|
||||
.deleted-projects-toggle
|
||||
| {% if show_deleted_projects %}
|
||||
a.hide-deleted(href="{{ request.base_url }}", title='Hide deleted projects')
|
||||
i.pi-trash
|
||||
| {% else %}
|
||||
a.show-deleted(href="{{ request.base_url }}?deleted=1", title='Show deleted projects')
|
||||
i.pi-trash
|
||||
| {% endif %}
|
||||
|
||||
ul.projects__list
|
||||
| {% for project in projects_deleted %}
|
||||
li.projects__list-item.deleted
|
||||
span.projects__list-thumbnail
|
||||
| {% if project.picture_square %}
|
||||
img(src="{{ project.picture_square.thumbnail('s', api=api) }}")
|
||||
| {% else %}
|
||||
i.pi-blender-cloud
|
||||
| {% endif %}
|
||||
.projects__list-details
|
||||
span.title {{ project.name }}
|
||||
ul.meta
|
||||
li.status.deleted Deleted
|
||||
li.edit
|
||||
a(href="javascript:undelete_project('{{ project._id }}')") Restore project
|
||||
| {% else %}
|
||||
| {% if show_deleted_projects %}
|
||||
li.projects__list-item.deleted You have no recenly deleted projects. Deleted projects can be restored within a month after deletion.
|
||||
| {% endif %}
|
||||
| {% endfor %}
|
||||
|
||||
| {% for project in projects_user %}
|
||||
li.projects__list-item(
|
||||
data-url="{{ url_for('projects.view', project_url=project.url) }}")
|
||||
a.projects__list-thumbnail(
|
||||
href="{{ url_for('projects.view', project_url=project.url) }}")
|
||||
| {% if project.picture_square %}
|
||||
img(src="{{ project.picture_square.thumbnail('s', api=api) }}")
|
||||
| {% else %}
|
||||
i.pi-blender-cloud
|
||||
| {% endif %}
|
||||
.projects__list-details
|
||||
a.title(href="{{ url_for('projects.view', project_url=project.url) }}")
|
||||
| {{ project.name }}
|
||||
|
||||
ul.meta
|
||||
li.status(
|
||||
class="{{ project.is_private | yesno('private,public,') }}",
|
||||
title="{{ project.is_private | yesno('Private Project,Public Project,') }}")
|
||||
| {{ project.is_private | yesno('Private,Public,') }}
|
||||
li.when(title="{{ project._created }}") {{ project._created | pretty_date }}
|
||||
li.edit
|
||||
a(href="{{ url_for('projects.edit', project_url=project.url) }}") Edit
|
||||
| {% if project.status == 'pending' and current_user.has_cap('view-pending-nodes') %}
|
||||
li.pending Not Published
|
||||
| {% endif %}
|
||||
| {% else %}
|
||||
| {% if current_user.has_cap('subscriber') %}
|
||||
li.projects__list-item(data-url="{{ url_for('projects.create') }}")
|
||||
a.projects__list-thumbnail
|
||||
i.pi-plus
|
||||
.projects__list-details
|
||||
a.title(href="{{ url_for('projects.create') }}")
|
||||
| Create a project to get started!
|
||||
| {% elif current_user.has_cap('can-renew-subscription') %}
|
||||
li.projects__list-item(data-url="https://store.blender.org/renew-my-subscription.php")
|
||||
a.projects__list-thumbnail
|
||||
i.pi-plus
|
||||
.projects__list-details
|
||||
a.title(href="https://store.blender.org/renew-my-subscription.php")
|
||||
| Renew your Blender Cloud subscription to create your own projects!
|
||||
| {% else %}
|
||||
li.projects__list-item(data-url="/join")
|
||||
a.projects__list-thumbnail
|
||||
i.pi-plus
|
||||
.projects__list-details
|
||||
a.title(href="/join")
|
||||
| Join Blender Cloud to create your own projects!
|
||||
| {% endif %}
|
||||
| {% endfor %}
|
||||
|
||||
section.nav-tabs__tab#shared(style='display: none')
|
||||
ul.projects__list
|
||||
| {% if projects_shared %}
|
||||
| {% for project in projects_shared %}
|
||||
li.projects__list-item(
|
||||
data-url="{{ url_for('projects.view', project_url=project.url) }}")
|
||||
a.projects__list-thumbnail(
|
||||
href="{{ url_for('projects.view', project_url=project.url) }}")
|
||||
| {% if project.picture_square %}
|
||||
img(src="{{ project.picture_square.thumbnail('s', api=api) }}")
|
||||
| {% else %}
|
||||
i.pi-blender-cloud
|
||||
| {% endif %}
|
||||
.projects__list-details
|
||||
a.title(href="{{ url_for('projects.view', project_url=project.url) }}")
|
||||
| {{ project.name }}
|
||||
|
||||
ul.meta
|
||||
li.status(
|
||||
class="{{ project.is_private | yesno('private,public,') }}",
|
||||
title="{{ project.is_private | yesno('Private Project,Public Project,') }}")
|
||||
| {{ project.is_private | yesno('Private,Public,') }}
|
||||
li.when {{ project._created | pretty_date }}
|
||||
li.who by {{ project.user.full_name }}
|
||||
li.edit
|
||||
a(href="{{ url_for('projects.edit', project_url=project.url) }}") Edit
|
||||
| {% if project.status == 'pending' and current_user.has_cap('view-pending-nodes') %}
|
||||
li.pending Not Published
|
||||
| {% endif %}
|
||||
|
||||
li.leave
|
||||
span.user-remove-prompt
|
||||
| Leave Project
|
||||
|
||||
span.user-remove
|
||||
| Are you sure?
|
||||
span.user-remove-confirm(
|
||||
user-id="{{ current_user.objectid }}",
|
||||
project-url="{{url_for('projects.sharing', project_url=project.url)}}")
|
||||
i.pi-check
|
||||
| Yes, leave
|
||||
span.user-remove-cancel
|
||||
i.pi-cancel
|
||||
| No, cancel
|
||||
|
||||
| {% endfor %}
|
||||
| {% else %}
|
||||
li.projects__list-item
|
||||
a.projects__list-thumbnail
|
||||
i.pi-heart-broken
|
||||
.projects__list-details
|
||||
.title
|
||||
| No projects shared with you... yet!
|
||||
| {% endif %}
|
||||
| {% endblock %}
|
||||
|
||||
|
||||
| {% block footer_scripts %}
|
||||
script.
|
||||
$(document).ready(function() {
|
||||
|
||||
$('li.projects__list-item').click(function(e){
|
||||
url = $(this).data('url');
|
||||
if (typeof url === 'undefined') return;
|
||||
|
||||
window.location.href = url;
|
||||
if (console) console.log(url);
|
||||
|
||||
$(this).addClass('active');
|
||||
$(this).find('.projects__list-thumbnail i')
|
||||
.removeAttr('class')
|
||||
.addClass('pi-spin spin');
|
||||
});
|
||||
|
||||
// Tabs behavior
|
||||
var $nav_tabs_list = $('#sub-nav-tabs__list');
|
||||
var $nav_tabs = $nav_tabs_list.find('a.nav-link');
|
||||
$nav_tabs.on('click', function(e){
|
||||
e.preventDefault();
|
||||
|
||||
$nav_tabs.removeClass('active');
|
||||
$(this).addClass('active');
|
||||
|
||||
$('.nav-tabs__tab').hide();
|
||||
$('#' + $(this).attr('data-tab-toggle')).show();
|
||||
});
|
||||
|
||||
// Leave project
|
||||
var $projects_list = $('ul.projects__list');
|
||||
$projects_list.find('span.user-remove-prompt').on('click', function(e){
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
|
||||
$(this).next().show();
|
||||
$(this).hide();
|
||||
});
|
||||
|
||||
$projects_list.find('span.user-remove-cancel').on('click', function(e){
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
|
||||
$(this).parent().prev().show();
|
||||
$(this).parent().hide();
|
||||
});
|
||||
|
||||
$projects_list.find('span.user-remove-confirm').on('click', function(e){
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
var parent = $(this).closest('.projects__list-item');
|
||||
|
||||
function removeUser(userId, projectUrl){
|
||||
$.post(projectUrl, {user_id: userId, action: 'remove'})
|
||||
.done(function (data) {
|
||||
parent.remove();
|
||||
});
|
||||
}
|
||||
|
||||
removeUser($(this).attr('user-id'), $(this).attr('project-url'));
|
||||
});
|
||||
|
||||
hopToTop(); // Display jump to top button
|
||||
});
|
||||
|
||||
|
||||
var patch_url = '{{ url_for('projects.patch.patch_project', project_id='PROJECTID') }}';
|
||||
function undelete_project(project_id) {
|
||||
console.log('undeleting project', project_id);
|
||||
$.ajax({
|
||||
url: patch_url.replace('PROJECTID', project_id),
|
||||
method: 'PATCH',
|
||||
data: JSON.stringify({'op': 'undelete'}),
|
||||
contentType: 'application/json'
|
||||
})
|
||||
.done(function(data, textStatus, jqXHR) {
|
||||
location.href = jqXHR.getResponseHeader('Location');
|
||||
})
|
||||
.fail(function(err) {
|
||||
toastr.error(xhrErrorResponseMessage(err), 'Undeletion failed');
|
||||
})
|
||||
}
|
||||
| {% endblock %}
|
195
src/templates/projects/landing.pug
Normal file
@@ -0,0 +1,195 @@
|
||||
| {% import 'projects/_macros.html' as projectmacros %}
|
||||
| {% extends 'layout.html' %}
|
||||
|
||||
| {% 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) }}")
|
||||
| {% else %}
|
||||
|
||||
| {% if node %}
|
||||
meta(property="og:title", content="{{ node.name }} - Blender Cloud")
|
||||
meta(name="twitter:title", content="{{ node.name }} on Blender Cloud")
|
||||
|
||||
| {% if node.node_type == 'post' %}
|
||||
|
||||
| {% if node.properties.content %}
|
||||
meta(property="og:description", content="{{ node.properties.content | truncate(180) }}")
|
||||
meta(name="twitter:description", content="{{ node.properties.content | truncate(180) }}")
|
||||
| {% else %}
|
||||
meta(property="og:description", content="Blender Cloud, your source for open content and training")
|
||||
meta(name="twitter:description", content="Blender Cloud, your source for open content and training")
|
||||
| {% endif %}
|
||||
|
||||
| {% else %}
|
||||
|
||||
| {% if node.description %}
|
||||
meta(property="og:description", content="{{ node.description | truncate(180) }}")
|
||||
meta(name="twitter:description", content="{{ node.description | truncate(180) }}")
|
||||
| {% else %}
|
||||
meta(property="og:description", content="Blender Cloud, your source for open content and training")
|
||||
meta(name="twitter:description", content="Blender Cloud, your source for open content and training")
|
||||
| {% endif %}
|
||||
|
||||
| {% endif %}
|
||||
|
||||
meta(property="og:url", content="{{url_for('projects.view_node', project_url=project.url, node_id=node._id)}}")
|
||||
| {% else %}
|
||||
meta(property="og:title", content="{{ project.name }} Blog on Blender Cloud")
|
||||
meta(name="twitter:title", content="{{ project.name }} Blog on Blender Cloud")
|
||||
meta(property="og:description", content="{{ project.summary }}")
|
||||
meta(name="twitter:description", content="{{ project.summary }}")
|
||||
|
||||
meta(property="og:url", content="{{url_for('projects.view', project_url=project.url, _external=True)}}")
|
||||
| {% endif %}
|
||||
|
||||
| {% endif %}
|
||||
| {% endblock og %}
|
||||
|
||||
| {% block page_overlay %}
|
||||
#page-overlay.video
|
||||
.video-embed
|
||||
#others
|
||||
| {% endblock %}
|
||||
|
||||
| {% block head %}
|
||||
|
||||
script(src="{{ url_for('static_pillar', filename='assets/js/vendor/videojs-6.2.8.min.js') }}")
|
||||
script(src="{{ url_for('static_pillar', filename='assets/js/vendor/videojs-ga-0.4.2.min.js') }}")
|
||||
script(src="{{ url_for('static_pillar', filename='assets/js/vendor/videojs-hotkeys-0.2.20.min.js') }}")
|
||||
| {% endblock %}
|
||||
|
||||
| {% block css %}
|
||||
link(href="{{ url_for('static_cloud', filename='assets/css/project-landing.css') }}", rel="stylesheet")
|
||||
| {% endblock %}
|
||||
|
||||
| {% block body %}
|
||||
header
|
||||
//a(href="{{ url_for( 'projects.view', project_url=project.url) }}")
|
||||
img.header(src="{{ project.picture_header.thumbnail('h', api=api) }}")
|
||||
|
||||
| {% block navbar_secondary %}
|
||||
| {{ projectmacros.render_secondary_navigation(project, pages=pages) }}
|
||||
|
||||
| {% endblock navbar_secondary %}
|
||||
#container.landing
|
||||
section.node-details-container.project
|
||||
.node-details-title
|
||||
h1 {{ project.name }}
|
||||
|
||||
| {% if project.description %}
|
||||
.node-details-description
|
||||
| {{ project | markdowned('description') }}
|
||||
| {% endif %}
|
||||
|
||||
|
||||
section.gallery
|
||||
h2 Gallery
|
||||
| {% for n in activity_stream %}
|
||||
| {% if n.node_type not in ['comment', 'post'] and n.picture %}
|
||||
.thumbnail.expand-image-links
|
||||
.img-container
|
||||
a(href="{{ n.picture.thumbnail('l', api=api) }}", data-node_id="{{ n._id }}")
|
||||
img(src="{{ n.picture.thumbnail('l', api=api) }}", alt="{{ n.name }}")
|
||||
.img-caption.table
|
||||
| {# Not using for the moment
|
||||
span.table-cell {{ n.name }}
|
||||
| #}
|
||||
| {% 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 %}
|
||||
a.btn(href="{{ url_for('projects.view_node', project_url=project.url, node_id=featured_node_id) }}") See more
|
||||
| {% endif %}
|
||||
|
||||
|
||||
section.node-extra
|
||||
h2 Latest Updates
|
||||
|
||||
| {% if activity_stream %}
|
||||
.node-updates
|
||||
ul.node-updates-list
|
||||
| {% for n in activity_stream %}
|
||||
| {% if n.node_type == 'post' %}
|
||||
li.node-updates-list-item(
|
||||
data-node_id="{{ n._id }}",
|
||||
class="{{ n.node_type }} {{ n.properties.content_type | hide_none }}")
|
||||
a.image(href="{{ url_for_node(node=n) }}")
|
||||
| {% if n.picture %}
|
||||
img(src="{{ n.picture.thumbnail('l', api=api) }}")
|
||||
| {% endif %}
|
||||
|
||||
.info
|
||||
a.title(href="{{ url_for_node(node=n) }}") {{ n.name }}
|
||||
p.description(href="{{ url_for_node(node=n) }}")
|
||||
| {% if n.node_type == 'post' %}
|
||||
| {{ n.properties | markdowned('content') | striptags | truncate(140, end="... <small>read more</small>") | safe | hide_none }}
|
||||
| {% else %}
|
||||
| {{ n | markdowned('description') | striptags | truncate(140, end="... <small>read more</small>") | safe | hide_none }}
|
||||
| {% endif %}
|
||||
//span.details
|
||||
// span.what {% if n.properties.content_type %}{{ n.properties.content_type | undertitle }}{% else %}{{ n.node_type | undertitle }}{% endif %} ·
|
||||
// span.when {{ n._updated | pretty_date }} by
|
||||
// span.who {{ n.user.full_name }}
|
||||
| {% endif %}
|
||||
| {% endfor %}
|
||||
| {% endif %}
|
||||
a.btn(href="{{ url_for('main.project_blog', project_url=project.url) }}") See all updates
|
||||
|
||||
| {% endblock body %}
|
||||
|
||||
|
||||
| {% 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('');
|
||||
}
|
||||
|
||||
$(document).click(function () {
|
||||
hideOverlay();
|
||||
});
|
||||
|
||||
$(document).keyup(function (e) {
|
||||
if (e.keyCode == 27) {
|
||||
hideOverlay();
|
||||
}
|
||||
});
|
||||
|
||||
$("a[data-node_id]").on( "click", function(e) {
|
||||
// var nodeId = $(this).data('node_id');
|
||||
// displayNode(nodeId);
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
$('#page-overlay').addClass('active');
|
||||
var url = $(this).attr('href');
|
||||
$('#page-overlay').html('<img src="' + url + '"/>')
|
||||
});
|
||||
|
||||
|
||||
| {% endblock %}
|
688
src/templates/projects/view.pug
Normal file
@@ -0,0 +1,688 @@
|
||||
| {% extends 'layout.html' %}
|
||||
| {% from '_macros/_add_new_menu.html' import add_new_menu %}
|
||||
include ../mixins/components
|
||||
|
||||
| {% block page_title %}{{ project.name }}{% endblock%}
|
||||
| {% set title = 'project' %}
|
||||
|
||||
| {% 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) }}")
|
||||
| {% else %}
|
||||
|
||||
| {% if node %}
|
||||
meta(property="og:title", content="{{ node.name }} - Blender Cloud")
|
||||
meta(name="twitter:title", content="{{ node.name }} on Blender Cloud")
|
||||
|
||||
| {% if node.node_type == 'post' %}
|
||||
|
||||
| {% if node.properties.content %}
|
||||
meta(property="og:description", content="{{ node.properties.content | truncate(180) }}")
|
||||
meta(name="twitter:description", content="{{ node.properties.content | truncate(180) }}")
|
||||
| {% else %}
|
||||
meta(property="og:description", content="Blender Cloud, your source for open content and training")
|
||||
meta(name="twitter:description", content="Blender Cloud, your source for open content and training")
|
||||
| {% endif %}
|
||||
|
||||
| {% else %}
|
||||
|
||||
| {% if node.description %}
|
||||
meta(property="og:description", content="{{ node.description | truncate(180) }}")
|
||||
meta(name="twitter:description", content="{{ node.description | truncate(180) }}")
|
||||
| {% else %}
|
||||
meta(property="og:description", content="Blender Cloud, your source for open content and training")
|
||||
meta(name="twitter:description", content="Blender Cloud, your source for open content and training")
|
||||
| {% endif %}
|
||||
|
||||
| {% endif %}
|
||||
|
||||
meta(property="og:url", content="{{url_for('projects.view_node', project_url=project.url, node_id=node._id)}}")
|
||||
| {% else %}
|
||||
meta(property="og:title", content="{{ project.name }} Blog on Blender Cloud")
|
||||
meta(name="twitter:title", content="{{ project.name }} Blog on Blender Cloud")
|
||||
meta(property="og:description", content="{{ project.summary }}")
|
||||
meta(name="twitter:description", content="{{ project.summary }}")
|
||||
|
||||
meta(property="og:url", content="{{url_for('projects.view', project_url=project.url, _external=True)}}")
|
||||
| {% endif %}
|
||||
|
||||
| {% endif %}
|
||||
| {% endblock %}
|
||||
|
||||
| {% block head %}
|
||||
link(href="{{ url_for('static_pillar', filename='assets/jstree/themes/default/style.min.css') }}", rel="stylesheet")
|
||||
| {% if node %}
|
||||
link(rel="amphtml", href="{{ url_for('nodes.view', node_id=node._id, _external=True, format='amp') }}")
|
||||
| {% endif %}
|
||||
|
||||
script(src="{{ url_for('static_pillar', filename='assets/js/vendor/videojs-6.2.8.min.js') }}")
|
||||
script(src="{{ url_for('static_pillar', filename='assets/js/vendor/videojs-ga-0.4.2.min.js') }}")
|
||||
script(src="{{ url_for('static_pillar', filename='assets/js/vendor/videojs-hotkeys-0.2.20.min.js') }}")
|
||||
script(src="{{ url_for('static_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_pillar', filename='assets/css/project-main.css') }}", rel="stylesheet")
|
||||
| {% endblock %}
|
||||
|
||||
| {% block navigation_tabs %}
|
||||
+nav-secondary()(class="bg-white")
|
||||
| {% if project.category == 'course' %}
|
||||
li.text-capitalize
|
||||
a.nav-link.text-muted.px-0(href="{{ url_for('cloud.courses') }}")
|
||||
| Courses
|
||||
| {% elif project.category == 'workshop' %}
|
||||
li.text-capitalize
|
||||
a.nav-link.text-muted.px-0(href="{{ url_for('cloud.workshops') }}")
|
||||
| Workshops
|
||||
li.px-1
|
||||
i.pi-angle-right
|
||||
| {% endif %}
|
||||
|
||||
+nav-secondary-link(
|
||||
class="px-0",
|
||||
href="{{url_for('projects.view', project_url=project.url, _external=True)}}")
|
||||
| {{ project.name }}
|
||||
|
||||
| {% if project.category == "open_project" %}
|
||||
+nav-secondary-link(
|
||||
class="active",
|
||||
href="{{url_for('projects.view', project_url=project.url, _external=True)}}")
|
||||
| Explore
|
||||
+nav-secondary-link(
|
||||
href="{{url_for('projects.view', project_url=project.url, _external=True)}}")
|
||||
| Blog
|
||||
+nav-secondary-link(
|
||||
href="{{url_for('projects.view', project_url=project.url, _external=True)}}")
|
||||
| About
|
||||
+nav-secondary-link(
|
||||
href="{{url_for('projects.view', project_url=project.url, _external=True)}}")
|
||||
| Team
|
||||
+nav-secondary-link(
|
||||
href="{{url_for('projects.view', project_url=project.url, _external=True)}}")
|
||||
| Awards
|
||||
| {% endif %}
|
||||
| {% endblock navigation_tabs %}
|
||||
|
||||
| {% block body %}
|
||||
#project-container
|
||||
#project-side-container
|
||||
#project_sidebar.bg-white
|
||||
ul.project-tabs.p-0
|
||||
//- li.tabs-thumbnail(class="{% if project.picture_square %}image{% endif %}")
|
||||
//- a(href="{{url_for('projects.view', project_url=project.url)}}")
|
||||
//- #project-loading
|
||||
//- i.pi-spin
|
||||
//- | {% if project.picture_square %}
|
||||
//- img(src="{{ project.picture_square.thumbnail('b', api=api) }}")
|
||||
//- | {% else %}
|
||||
//- i.pi-home
|
||||
//- | {% endif %}
|
||||
|
||||
li.tabs-browse(
|
||||
title="Browse",
|
||||
data-toggle="tooltip",
|
||||
data-placement="right",
|
||||
class="active")
|
||||
a(href="{{url_for('projects.view', project_url=project.url, _external=True)}}")
|
||||
i.pi-folder
|
||||
|
||||
| {% if not project.is_private %}
|
||||
| {% if current_user_is_subscriber %}
|
||||
li.tabs-search(
|
||||
title="Search",
|
||||
data-toggle="tooltip",
|
||||
data-placement="right")
|
||||
a(href="{{ url_for('projects.search', project_url=project.url, _external=True)}} ")
|
||||
i.pi-search
|
||||
| {% else %}
|
||||
li.tabs-search(
|
||||
title="Search (subscribers only)",
|
||||
data-toggle="tooltip",
|
||||
data-placement="right")
|
||||
a(href="{{ url_for('cloud.join') }}")
|
||||
i.pi-search
|
||||
| {% endif %}
|
||||
| {% endif %}
|
||||
| {{ extension_sidebar_links }}
|
||||
|
||||
| {% if project.has_method('PUT') %}
|
||||
li(
|
||||
title="Edit Project",
|
||||
data-toggle="tooltip",
|
||||
data-placement="right")
|
||||
a(href="{{ url_for('projects.edit', project_url=project.url) }}")
|
||||
i.pi-cog
|
||||
| {% endif %}
|
||||
|
||||
|
||||
#project_nav(class="{{ title }}")
|
||||
#project_nav-container
|
||||
| {% if title != 'about' %}
|
||||
//- +nav-secondary(class="bg-white")
|
||||
//- +nav-secondary-link(
|
||||
//- class="active",
|
||||
//- href="{{url_for('projects.view', project_url=project.url, _external=True)}}")
|
||||
//- | {{ project.name }}
|
||||
//- #project_nav-header.bg-white
|
||||
|
||||
//- a.project-title.p-2.font-weight-bold.text-dark(
|
||||
//- href="{{url_for('projects.view', project_url=project.url, _external=True)}}")
|
||||
//- | {{ project.name }}
|
||||
|
||||
| {% block project_tree %}
|
||||
#project_tree.bg-white
|
||||
| {% endblock project_tree %}
|
||||
| {% endif %}
|
||||
|
||||
|
||||
#project_context-container.border-left
|
||||
| {% if project.has_method('PUT') %}
|
||||
#project_context-header.bg-white
|
||||
span#status-bar
|
||||
|
||||
ul.project-edit-tools.disabled
|
||||
li.dropdown
|
||||
button#item_add.project-mode-view.btn.btn-sm.btn-outline-secondary.dropdown-toggle(
|
||||
type="button",
|
||||
data-toggle="dropdown",
|
||||
aria-haspopup="true",
|
||||
aria-expanded="false")
|
||||
i.button-add-icon.pi-collection-plus
|
||||
| New...
|
||||
|
||||
ul.dropdown-menu.add_new-menu
|
||||
| {{ add_new_menu(project.node_types) }}
|
||||
|
||||
li.button-edit
|
||||
a#item_edit.project-mode-view.btn.btn-sm.btn-outline-secondary.ml-2(
|
||||
href="javascript:void(0);",
|
||||
title="Edit",
|
||||
data-project_id="{{project._id}}")
|
||||
i.button-edit-icon.pi-edit
|
||||
| Edit Project
|
||||
|
||||
li.dropdown
|
||||
button.dropdown-toggle.project-mode-view.btn.btn-sm.btn-outline-secondary.mx-2(
|
||||
type="button",
|
||||
data-toggle="dropdown",
|
||||
aria-haspopup="true",
|
||||
aria-expanded="false")
|
||||
i.pi-more-vertical.p-0
|
||||
|
||||
ul.dropdown-menu
|
||||
| {% if current_user.has_cap('admin') %}
|
||||
li.dropdown-item
|
||||
a#item_featured(
|
||||
href="javascript:void(0);",
|
||||
title="Feature on project's homepage",
|
||||
data-toggle="tooltip",
|
||||
data-placement="left")
|
||||
i.pi-star
|
||||
| Toggle Featured
|
||||
|
||||
li.dropdown-item
|
||||
a#item_toggle_public(
|
||||
href="javascript:void(0);",
|
||||
title="Make it accessible to anyone",
|
||||
data-toggle="tooltip",
|
||||
data-placement="left")
|
||||
i.pi-lock-open
|
||||
| Toggle Public
|
||||
| {% endif %}
|
||||
|
||||
li.dropdown-item
|
||||
a#item_toggle_projheader(
|
||||
href="javascript:void(0);",
|
||||
title="Feature as project's header",
|
||||
data-toggle="tooltip",
|
||||
data-placement="left")
|
||||
i.pi-star
|
||||
| Toggle Project Header video
|
||||
|
||||
li.dropdown-item.button-move
|
||||
a#item_move(
|
||||
href="javascript:void(0);",
|
||||
title="Move into a folder...",
|
||||
data-toggle="tooltip",
|
||||
data-placement="left")
|
||||
i.button-move-icon.pi-move
|
||||
| Move
|
||||
|
||||
li.dropdown-item.button-delete
|
||||
a#item_delete(
|
||||
href="javascript:void(0);",
|
||||
title="Can be undone within a month",
|
||||
data-toggle="tooltip",
|
||||
data-placement="left")
|
||||
i.pi-trash
|
||||
| Delete Project
|
||||
|
||||
// Edit Mode
|
||||
li.button-cancel
|
||||
a#item_cancel.project-mode-edit.btn.btn-outline-secondary(
|
||||
href="javascript:void(0);",
|
||||
title="Cancel changes")
|
||||
i.button-cancel-icon.pi-cancel
|
||||
| Cancel
|
||||
|
||||
li.button-save
|
||||
a#item_save.project-mode-edit.btn.btn-outline-success.mx-2(
|
||||
href="javascript:void(0);",
|
||||
title="Save changes")
|
||||
i.button-save-icon.pi-check
|
||||
| Save Changes
|
||||
|
||||
| {% endif %}
|
||||
|
||||
| {% set utm_source = request.args.get('utm_source') %}
|
||||
| {% if config.UTM_LINKS and utm_source in config.UTM_LINKS %}
|
||||
#utm_container
|
||||
a(href="{{config.UTM_LINKS[utm_source]['link']}}")
|
||||
img(src="{{config.UTM_LINKS[utm_source]['image']}}", alt="gift", class="img-responsive")
|
||||
| {% endif %}
|
||||
#project_context
|
||||
| {% block project_context %}
|
||||
| {% if show_project %}
|
||||
| {% include "projects/view_embed.html" %}
|
||||
| {% endif %}
|
||||
| {% endblock project_context %}
|
||||
|
||||
#overlay-mode-move-container
|
||||
.overlay-container
|
||||
.title
|
||||
i.pi-angle-left
|
||||
| Select the <strong>folder</strong> where you want to move it
|
||||
.buttons
|
||||
button#item_move_accept.move.disabled
|
||||
| Select a Folder
|
||||
button#item_move_cancel.cancel
|
||||
i.pi-cancel
|
||||
| Cancel
|
||||
|
||||
| {% endblock %}
|
||||
|
||||
| {% block footer_container %}{% endblock %}
|
||||
|
||||
| {% block footer_scripts_pre %}
|
||||
|
||||
| {% if project.has_method('PUT') %}
|
||||
| {# JS containing the Edit, Add, Featured, and Move functions #}
|
||||
script(type="text/javascript", src="{{ url_for('static_pillar', filename='assets/js/project-edit.min.js') }}")
|
||||
| {% endif %}
|
||||
|
||||
script.
|
||||
function updateToggleProjHeaderMenuItem() {
|
||||
var $toggle_projheader = $('#item_toggle_projheader');
|
||||
|
||||
if (ProjectUtils.isProject()) {
|
||||
$toggle_projheader.hide();
|
||||
return;
|
||||
}
|
||||
if (ProjectUtils.nodeType() == 'asset') {
|
||||
$toggle_projheader.show();
|
||||
} else {
|
||||
$toggle_projheader.hide();
|
||||
}
|
||||
}
|
||||
$(updateToggleProjHeaderMenuItem);
|
||||
|
||||
// Function to update the interface on loadNodeContent, and edit/saving assets
|
||||
function updateUi(nodeId, mode) {
|
||||
|
||||
if (mode === 'view') {
|
||||
$('.project-mode-view').displayAs('inline-block');
|
||||
$('.project-mode-edit').hide();
|
||||
|
||||
$("#node-edit-form").unbind("submit");
|
||||
$("#item_save").unbind("click");
|
||||
$("#item_cancel").unbind("click");
|
||||
} else if (mode === 'edit') {
|
||||
$('.project-mode-view').hide();
|
||||
$('.project-mode-edit').displayAs('inline-block');
|
||||
} else {
|
||||
if (console) console.log('Invalid mode:', mode);
|
||||
}
|
||||
|
||||
// Prevent flicker by scrolling to top.
|
||||
$("#project_context-container").scrollTop(0);
|
||||
|
||||
// Enable specific items under the Add New dropdown
|
||||
if (ProjectUtils.nodeType() === 'group') {
|
||||
addMenuEnable(['asset', 'group']);
|
||||
|
||||
} else if (ProjectUtils.nodeType() === 'group_texture') {
|
||||
addMenuEnable(['group_texture', 'texture']);
|
||||
|
||||
} else if (ProjectUtils.nodeType() === 'group_hdri') {
|
||||
addMenuEnable(['group_hdri', 'hdri']);
|
||||
|
||||
} else if (!ProjectUtils.isProject()) {
|
||||
addMenuEnable(false);
|
||||
}
|
||||
|
||||
updateToggleProjHeaderMenuItem();
|
||||
|
||||
// Set the page title on the document
|
||||
var page_title = $('#node-title').text() + " - {{ project.name }} — Blender Cloud";
|
||||
DocumentTitleAPI.set_page_title(page_title);
|
||||
|
||||
// TODO: Maybe remove this, now it's also in loadNodeContent(), but double-check
|
||||
// it's done like that in all users of updateUi().
|
||||
$('#project-loading').removeAttr('class');
|
||||
}
|
||||
| {% endblock %}
|
||||
|
||||
| {% block footer_scripts %}
|
||||
script(src="{{ url_for('static_pillar', filename='assets/jstree/jstree.min.js') }}")
|
||||
|
||||
script.
|
||||
{% if show_project %}
|
||||
ProjectUtils.setProjectAttributes({projectId: "{{project._id}}", isProject: true, nodeId: ''});
|
||||
{% else %}
|
||||
{% if node %}
|
||||
ProjectUtils.setProjectAttributes({projectId: "{{project._id}}", isProject: false, nodeId: '{{node._id}}'});
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
var projectTree = document.getElementById('project_tree');
|
||||
|
||||
var urlNodeMove = "{{url_for('projects.move_node')}}";
|
||||
var urlNodeFeature = "{{url_for('projects.add_featured_node')}}";
|
||||
var urlNodeDelete = "{{url_for('projects.delete_node')}}";
|
||||
var urlNodeTogglePublic = "{{url_for('projects.toggle_node_public')}}";
|
||||
var urlNodeToggleProjHeader = "{{url_for('projects.toggle_node_project_header')}}";
|
||||
var urlProjectDelete = "{{url_for('projects.delete')}}";
|
||||
var urlProjectEdit = "{{url_for('projects.edit', project_url=project.url)}}";
|
||||
|
||||
|
||||
function loadNodeContent(url, nodeId) {
|
||||
$('#project-loading').addClass('active');
|
||||
$.get(url, function(dataHtml) {
|
||||
// Update the DOM injecting the generate HTML into the page
|
||||
$('#project_context').html(dataHtml);
|
||||
})
|
||||
.done(function(){
|
||||
updateUi(nodeId, 'view');
|
||||
})
|
||||
.fail(function(dataResponse) {
|
||||
$('#project_context').html($('<iframe id="server_error"/>'));
|
||||
$('#server_error').attr('src', url);
|
||||
})
|
||||
.always(function(){
|
||||
$('#project-loading').removeAttr('class');
|
||||
$('.button-edit-icon').addClass('pi-edit').removeClass('pi-spin spin');
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function loadProjectContent(url) {
|
||||
$('#project-loading').addClass('active');
|
||||
|
||||
$.get(url, function(dataHtml) {
|
||||
// Update the DOM injecting the generated HTML into the page
|
||||
$('#project_context').html(dataHtml);
|
||||
})
|
||||
.done(function() {
|
||||
updateUi('', 'view');
|
||||
addMenuEnable();
|
||||
addMenuDisable(['texture']);
|
||||
})
|
||||
.fail(function(dataResponse) {
|
||||
$('#project_context').html($('<iframe id="server_error"/>'));
|
||||
$('#server_error').attr('src', url);
|
||||
})
|
||||
.always(function(){
|
||||
$('#project-loading').removeAttr('class');
|
||||
$('.button-edit-icon').addClass('pi-edit').removeClass('pi-spin spin');
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function displayStorage(storageNodeId, path) {
|
||||
var url = '/nodes/' + storageNodeId + '/view?path=' + path;
|
||||
loadNodeContent(url);
|
||||
}
|
||||
|
||||
|
||||
function displayNode(nodeId, pushState) {
|
||||
// Remove the 'n_' suffix from the id
|
||||
if (nodeId.substring(0, 2) == 'n_') {
|
||||
nodeId = nodeId.substr(2);
|
||||
}
|
||||
|
||||
var url = '/nodes/' + nodeId + '/view';
|
||||
loadNodeContent(url, nodeId);
|
||||
|
||||
// Determine whether we should push the new state or not.
|
||||
pushState = (typeof pushState !== 'undefined') ? pushState : true;
|
||||
if (!pushState) return;
|
||||
|
||||
// Push the correct URL onto the history.
|
||||
var push_state = {nodeId: nodeId, url: url};
|
||||
var push_url = '{{url_for("projects.view", project_url=project.url)}}' + nodeId;
|
||||
// console.log('Pushing state ', push_state, ' with URL ', push_url);
|
||||
window.history.pushState(
|
||||
push_state,
|
||||
'Node ' + nodeId, // TODO: use sensible title
|
||||
push_url
|
||||
);
|
||||
}
|
||||
|
||||
function redirectToNode(nodeId) {
|
||||
var generic_url = '{{ url_for("projects.view_node", project_url=project.url, node_id="theNodeId") }}';
|
||||
var node_url = generic_url.replace('theNodeId', nodeId);
|
||||
|
||||
// This makes the user skip the current page when using the 'back' button,
|
||||
// i.e. it works as a proper redirect.
|
||||
location.replace(node_url);
|
||||
}
|
||||
|
||||
window.onpopstate = function(event) {
|
||||
var state = event.state;
|
||||
// console.log('State popped. location:', document.location, 'state:', state);
|
||||
|
||||
// Deselect any selected node. We'll select the visited node (if any) later on.
|
||||
var jstreeAPI = $(projectTree).jstree(true);
|
||||
jstreeAPI.deselect_all(true);
|
||||
|
||||
if (state == null) {
|
||||
// Went back to the project.
|
||||
displayProject();
|
||||
return;
|
||||
}
|
||||
|
||||
// Went back to a node.
|
||||
loadNodeContent(state.url, state.nodeId);
|
||||
|
||||
// Annoying hack because jstreeAPI.select_node() can only suppress the
|
||||
// changed.jstree event, and NOT the selected_node.jstree event.
|
||||
projectTree.dataset.ignoreSelectNode = true;
|
||||
jstreeAPI.select_node('n_' + state.nodeId, true);
|
||||
delete projectTree.dataset.ignoreSelectNode;
|
||||
};
|
||||
|
||||
function displayProject() {
|
||||
var url = "{{url_for('projects.view', project_url=project.url, embed=1)}}";
|
||||
loadProjectContent(url);
|
||||
}
|
||||
|
||||
|
||||
function getHashId() {
|
||||
if (console)
|
||||
console.log('getHashId() should not be used any more!');
|
||||
}
|
||||
|
||||
/* Loaded once, on page load */
|
||||
function loadContent() {
|
||||
|
||||
var nodeId = ProjectUtils.nodeId();
|
||||
var isProject = ProjectUtils.isProject();
|
||||
if (isProject) {
|
||||
// No need to asynchronously load the project, as it's embedded by Jinja.
|
||||
// displayProject() is still needed, though, when people use 'back' to go there.
|
||||
if (location.hash) {
|
||||
// Handle old-style /p/{url}/#node-ID links, and redirect them to the correct spot.
|
||||
redirectToNode(location.hash.substr(1));
|
||||
}
|
||||
$('.project-mode-view').displayAs('inline-block');
|
||||
$('.project-mode-edit').hide();
|
||||
} else {
|
||||
displayNode(nodeId, false);
|
||||
}
|
||||
|
||||
$(projectTree).jstree({
|
||||
'core': {
|
||||
'data': function (obj, callback) {
|
||||
if(obj.id === '#') { //tree root
|
||||
if (isProject) {
|
||||
$.getJSON("{{url_for('projects.jstree', project_url=project.url)}}", function (jsonObject) {
|
||||
callback.call(this, jsonObject['items']);
|
||||
});
|
||||
} else {
|
||||
$.getJSON('/nodes/' + nodeId + '/jstree', function(jsonObject) {
|
||||
callback.call(this, jsonObject['items']);
|
||||
});
|
||||
}
|
||||
} else { //normal node
|
||||
var childNodeId;
|
||||
if (obj.original.type == 'group_storage') {
|
||||
childNodeId = obj.original.storage_node;
|
||||
$.getJSON('/nodes/' + childNodeId + '/jstree?children=1&path=' + obj.original.path, function(jsonObject) {
|
||||
callback.call(this, jsonObject.children);
|
||||
});
|
||||
} else {
|
||||
// Remove the 'n_' suffix from the id
|
||||
childNodeId = obj.id.substring(2);
|
||||
$.getJSON('/nodes/' + childNodeId + '/jstree?children=1', function(jsonObject) {
|
||||
callback.call(this, jsonObject.children);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"types" : {
|
||||
"#": {"valid_children": ["collection"]},
|
||||
"chapter" : {"icon": "pi-folder"},
|
||||
"group" : {"icon": "pi-folder"},
|
||||
"group_texture" : {"icon": "pi-folder-texture"},
|
||||
"group_hdri" : {"icon": "pi-folder-texture", "max_children": 0},
|
||||
"group_storage" : {"icon": "pi-folder"},
|
||||
"filesystem_node" : {"icon": "pi-folder"},
|
||||
"file" : {"icon": "pi-document", "max_children": 0},
|
||||
"filesystem_file" : {"icon": "pi-document", "max_children": 0},
|
||||
"image" : {"icon": "pi-image", "max_children": 0},
|
||||
"hdri" : {"icon": "pi-globe", "max_children": 0},
|
||||
"texture" : {"icon": "pi-texture", "max_children": 0},
|
||||
"video" : {"icon": "pi-play", "max_children": 0},
|
||||
"blog" : {"icon": "pi-newspaper", "max_children": 0},
|
||||
"page" : {"icon": "pi-document-text", "max_children": 0},
|
||||
"default" : {"icon": "pi-document"}
|
||||
},
|
||||
"plugins": ["types",] //, "state", "sort"
|
||||
});
|
||||
|
||||
|
||||
var jstreeAPI = $(projectTree).jstree(true);
|
||||
|
||||
$(projectTree).on("select_node.jstree", function (e, data) {
|
||||
var selectedNodeId = data.node.id.substr(2);
|
||||
|
||||
// Ignore events that can't be suppressed otherwise.
|
||||
// This can be removed if jstreeAPI.select_node() allows suppressing
|
||||
// the select_node.jstree event.
|
||||
if (e.target.dataset.ignoreSelectNode === 'true') return;
|
||||
|
||||
if (typeof(data.node.original.path) === 'undefined') {
|
||||
var movingMode = Cookies.getJSON('bcloud_moving_node');
|
||||
|
||||
// Check if we are in the process of moving a node
|
||||
if (movingMode) {
|
||||
// Allow moving nodes only inside of node_type group
|
||||
if (data.node.original.type != 'group' || movingMode.node_id === selectedNodeId || movingMode.node_id === ProjectUtils.parentNodeId()) {
|
||||
|
||||
if (movingMode.node_type === 'texture') {
|
||||
|
||||
if (data.node.original.type === 'group_texture') {
|
||||
$('#item_move_accept').html('<i class="pi-check"></i>Move Here').removeClass('disabled');
|
||||
} else {
|
||||
$('#item_move_accept').html('Select a Texture Folder').addClass('disabled');
|
||||
}
|
||||
|
||||
} else if (movingMode.node_type === 'hdri') {
|
||||
|
||||
if (data.node.original.type === 'group_hdri') {
|
||||
$('#item_move_accept').html('<i class="pi-check"></i>Move Here').removeClass('disabled');
|
||||
} else {
|
||||
$('#item_move_accept').html('Select an HDRi Folder').addClass('disabled');
|
||||
}
|
||||
|
||||
} else {
|
||||
$('#item_move_accept').html('Select a Folder').addClass('disabled');
|
||||
}
|
||||
|
||||
} else {
|
||||
$('#item_move_accept').html('<i class="pi-check"></i>Move Here').removeClass('disabled');
|
||||
}
|
||||
}
|
||||
|
||||
// Check the type of node and act accordingly
|
||||
if (data.node.original.custom_view) {
|
||||
window.location = data.node.a_attr.href;
|
||||
} else {
|
||||
var currentNodeId = ProjectUtils.nodeId();
|
||||
if (currentNodeId != selectedNodeId) {
|
||||
displayNode(selectedNodeId);
|
||||
}
|
||||
|
||||
jstreeAPI.open_node(data.node);
|
||||
}
|
||||
} else {
|
||||
displayStorage(data.node.original.storage_node, data.node.original.path);
|
||||
jstreeAPI.toggle_node(data.node);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
{% if is_embedded_edit is not defined or is_embedded_edit %}
|
||||
// Initialize the page if we are not directly editing a node (most of the time)
|
||||
loadContent();
|
||||
{% endif %}
|
||||
|
||||
var project_container = document.getElementById('project-container');
|
||||
|
||||
/* UI Stuff */
|
||||
$(window).on("load resize",function(){
|
||||
containerResizeY($(window).height());
|
||||
|
||||
if ($(window).width() > 480) {
|
||||
project_container.style.height = (window.innerHeight - project_container.offsetTop) + "px";
|
||||
}
|
||||
});
|
||||
|
||||
{% if current_user_is_subscriber %}
|
||||
$(projectTree).addClass('is_subscriber');
|
||||
{% endif %}
|
||||
|
||||
| {% endblock %}
|
||||
|
||||
| {% block comment_scripts %} {% endblock%}
|
@@ -1,5 +1,7 @@
|
||||
| {% extends 'layout.html' %}
|
||||
|
||||
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' %}
|
||||
@@ -9,13 +11,13 @@
|
||||
| {% 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_caminandes_3_03.jpg') %}
|
||||
| {% set page_header_image = url_for('static', filename='assets/img/backgrounds/background_agent327_04.jpg') %}
|
||||
| {% set page_header_text = 'Character modeling, 3D printing, VFX, rigging and more.' %}
|
||||
|
||||
| {% elif title == 'workshops' %}
|
||||
| {% set page_title = 'Workshops' %}
|
||||
| {% set page_description = 'Production quality training by 3D professionals' %}
|
||||
| {% set page_header_image = url_for('static', filename='assets/img/backgrounds/background_caminandes_3_03.jpg') %}
|
||||
| {% set page_header_image = url_for('static', filename='assets/img/backgrounds/background_agent327_04.jpg') %}
|
||||
| {% set page_header_text = 'Enter the artist workshop and learn by example.' %}
|
||||
|
||||
| {% endif %}
|
||||
@@ -38,59 +40,70 @@ meta(name="twitter:image", content="{{ page_header_image }}")
|
||||
| {{ page_title }}
|
||||
| {% endblock %}
|
||||
|
||||
| {% block navigation_tabs %}
|
||||
|
||||
| {% if title in ['courses', 'workshops'] %}
|
||||
+nav-secondary
|
||||
+nav-secondary-link(
|
||||
class="{% if title == 'workshops' %}active{% endif %}",
|
||||
href="{{ url_for('cloud.workshops') }}")
|
||||
| Workshops
|
||||
|
||||
+nav-secondary-link(
|
||||
class="{% if title == 'courses' %}active{% endif %}",
|
||||
href="{{ url_for('cloud.courses') }}")
|
||||
| Courses
|
||||
|
||||
+nav-secondary-link(
|
||||
class="{% if title == 'gallery' %}active{% endif %}",
|
||||
href="{{ url_for('projects.view', project_url='gallery') }}")
|
||||
| Art Gallery
|
||||
| {% endif %}
|
||||
|
||||
| {% endblock navigation_tabs %}
|
||||
|
||||
| {% block body %}
|
||||
|
||||
#project-container
|
||||
.container.pb-5
|
||||
.pt-4
|
||||
h2.text-uppercase.font-weight-bold
|
||||
| {{ page_title }}
|
||||
.lead
|
||||
| {{ page_header_text }}
|
||||
|
||||
#node_index-container
|
||||
#node_index-header.collection
|
||||
img.background-header(src="{{ page_header_image }}")
|
||||
#node_index-collection-info
|
||||
.node_index-collection-name
|
||||
span {{ page_title }}
|
||||
.node_index-collection-description
|
||||
span.
|
||||
{{ page_header_text }}
|
||||
|
||||
.node_index-collection
|
||||
hr.pb-2
|
||||
|
||||
+card-deck(3)
|
||||
| {% for project in projects %}
|
||||
| {% if (project.status == 'published') or (project.status == 'pending' and current_user.is_authenticated) and project._id != config.MAIN_PROJECT_ID %}
|
||||
|
||||
.node_index-collection-card.project(
|
||||
data-url="{{ url_for('projects.view', project_url=project.url) }}",
|
||||
tabindex="{{ loop.index }}")
|
||||
| {% if (project.status == 'published') or (project.status == 'pending' and current_user.is_authenticated) and project._id != config.MAIN_PROJECT_ID %}
|
||||
+card(data-url="{{ url_for('projects.view', project_url=project.url) }}", tabindex='{{ loop.index }}')
|
||||
| {% if project.picture_header %}
|
||||
a.item-header(
|
||||
href="{{ url_for('projects.view', project_url=project.url) }}")
|
||||
img(src="{{ project.picture_header.thumbnail('l', api=api) }}")
|
||||
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 }}")
|
||||
| {% endif %}
|
||||
|
||||
.item-info
|
||||
a.item-title(
|
||||
href="{{ url_for('projects.view', project_url=project.url) }}")
|
||||
| {{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 %}
|
||||
|
||||
| {% if project.summary %}
|
||||
p.item-description
|
||||
p.card-text
|
||||
| {{project.summary|safe}}
|
||||
| {% endif %}
|
||||
|
||||
a.learn-more LEARN MORE
|
||||
|
||||
| {% endif %}
|
||||
| {% endfor %}
|
||||
|
||||
|
||||
| {% endblock %}
|
||||
|
||||
|
||||
| {% block footer_scripts %}
|
||||
script.
|
||||
$('.node_index-collection-card.project').on('click', function(e){
|
||||
$('.js-project-go').on('click', function(e){
|
||||
e.preventDefault();
|
||||
window.location.href = $(this).data('url');
|
||||
});
|
||||
|
@@ -1,6 +1,7 @@
|
||||
| {% extends 'layout.html' %}
|
||||
| {% block page_title %}Services{% endblock %}
|
||||
| {% set title = 'services' %}
|
||||
include mixins/components
|
||||
|
||||
| {% block og %}
|
||||
meta(property="og:type", content="website")
|
||||
@@ -20,27 +21,11 @@ meta(name="twitter:image", content="{{ url_for('static', filename='assets/img/ba
|
||||
| {% endblock %}
|
||||
|
||||
| {% block body %}
|
||||
#page-container
|
||||
#page-header(style="background-image: url({{ url_for('static', filename='assets/img/backgrounds/services_projects.jpg')}})")
|
||||
.container
|
||||
.page-title
|
||||
| Blender Cloud Services
|
||||
.page-title-summary
|
||||
span.text-background
|
||||
p.
|
||||
Blender Cloud is the creative hub for your projects, powered by Free and Open Source software.
|
||||
- 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')}}")
|
||||
|
||||
p.
|
||||
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!
|
||||
|
||||
.navbar-backdrop-overlay
|
||||
|
||||
- var addon_text = 'Available through the <a href="{{ url_for(\'cloud.services\') }}#blender-cloud-add-on">Blender Cloud add-on</a>'
|
||||
|
||||
#page-content
|
||||
|
||||
section#blender-cloud-add-on.page-card
|
||||
- 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
|
||||
.page-card-side
|
||||
h2.page-card-title
|
||||
| Blender Cloud add-on
|
||||
@@ -54,7 +39,7 @@ meta(name="twitter:image", content="{{ url_for('static', filename='assets/img/ba
|
||||
|
||||
small Blender Cloud add-on requires Blender 2.78 or newer
|
||||
|
||||
a.page-card-cta.download(
|
||||
a.btn.btn-outline-success(
|
||||
href="https://cloud.blender.org/r/downloads/blender_cloud-latest-addon.zip")
|
||||
i.pi-download
|
||||
| Download add-on <small>v</small> {{ config.BLENDER_CLOUD_ADDON_VERSION }}
|
||||
@@ -63,7 +48,7 @@ meta(name="twitter:image", content="{{ url_for('static', filename='assets/img/ba
|
||||
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.right
|
||||
.page-card-side
|
||||
h2.page-card-title Blender Sync
|
||||
.page-card-summary
|
||||
@@ -86,7 +71,7 @@ meta(name="twitter:image", content="{{ url_for('static', filename='assets/img/ba
|
||||
src="{{ url_for('static', filename='assets/img/features/sync_thumbnail.jpg')}}")
|
||||
|
||||
|
||||
section#texture-browser.page-card.right
|
||||
section#texture-browser.page-card.right
|
||||
.page-card-side
|
||||
h2.page-card-title Texture & HDRI Browser
|
||||
.page-card-summary
|
||||
@@ -97,7 +82,7 @@ meta(name="twitter:image", content="{{ url_for('static', filename='assets/img/ba
|
||||
|
||||
.tip !{addon_text}
|
||||
|
||||
a.page-card-cta.js-watch-video.download(
|
||||
a.btn.btn-outline-success.js-watch-video(
|
||||
href="https://www.youtube.com/watch?v=-srXYv2Osjw",
|
||||
data-youtube-id="-srXYv2Osjw")
|
||||
i.pi-play
|
||||
@@ -108,7 +93,7 @@ meta(name="twitter:image", content="{{ url_for('static', filename='assets/img/ba
|
||||
src="{{ url_for('static', filename='assets/img/features/tex_library_thumbnail.jpg')}}")
|
||||
|
||||
|
||||
section#image-sharing.page-card.right
|
||||
section#image-sharing.page-card.right
|
||||
.page-card-side
|
||||
h2.page-card-title Image Sharing
|
||||
.page-card-summary
|
||||
@@ -117,7 +102,7 @@ meta(name="twitter:image", content="{{ url_for('static', filename='assets/img/ba
|
||||
|
||||
.tip !{addon_text}
|
||||
|
||||
a.page-card-cta.download.js-watch-video(
|
||||
a.btn.btn-outline-success.js-watch-video(
|
||||
href="https://www.youtube.com/watch?v=yvtqeMBOAyk",
|
||||
data-youtube-id="yvtqeMBOAyk")
|
||||
i.pi-play
|
||||
@@ -132,7 +117,7 @@ meta(name="twitter:image", content="{{ url_for('static', filename='assets/img/ba
|
||||
src="{{ url_for('static', filename='assets/img/features/image_sharing_thumbnail.jpg')}}")
|
||||
|
||||
|
||||
section#projects.page-card.right
|
||||
section#projects.page-card.right
|
||||
.page-card-side
|
||||
h2.page-card-title Private Projects
|
||||
.page-card-summary.
|
||||
@@ -148,14 +133,14 @@ meta(name="twitter:image", content="{{ url_for('static', filename='assets/img/ba
|
||||
src="{{ url_for('static', filename='assets/img/features/projects_thumbnail.jpg')}}")
|
||||
|
||||
|
||||
section#attract.page-card.right
|
||||
section#attract.page-card.right
|
||||
.page-card-side
|
||||
h2.page-card-title
|
||||
| Attract
|
||||
.page-card-summary.
|
||||
Production-management software for your film, game, or commercial projects.
|
||||
|
||||
a.page-card-cta.download.js-watch-video(
|
||||
a.btn.btn-outline-success.js-watch-video(
|
||||
href="https://www.youtube.com/watch?v=b9x1rlyyt_o",
|
||||
data-youtube-id="b9x1rlyyt_o")
|
||||
i.pi-play
|
||||
@@ -171,14 +156,14 @@ meta(name="twitter:image", content="{{ url_for('static', filename='assets/img/ba
|
||||
src="{{ url_for('static', filename='assets/img/features/attract_thumbnail.jpg')}}")
|
||||
|
||||
|
||||
section#flamenco.page-card.right
|
||||
section#flamenco.page-card.right
|
||||
.page-card-side
|
||||
h2.page-card-title
|
||||
| Flamenco
|
||||
.page-card-summary.
|
||||
Take control of your computing infrastructure and get things done.
|
||||
|
||||
a.page-card-cta.download.js-watch-video(
|
||||
a.btn.btn-outline-success.js-watch-video(
|
||||
href="https://www.youtube.com/watch?v=7cnFKhsM67Q",
|
||||
data-youtube-id="7cnFKhsM67Q")
|
||||
i.pi-play
|
||||
@@ -194,30 +179,33 @@ meta(name="twitter:image", content="{{ url_for('static', filename='assets/img/ba
|
||||
src="{{ url_for('static', filename='assets/img/features/flamenco_thumbnail.jpg')}}")
|
||||
|
||||
|
||||
| {% if not current_user.has_role('subscriber') %}
|
||||
section.page-card.subscribe(
|
||||
| {% if not current_user.has_role('subscriber') %}
|
||||
section.page-card(
|
||||
style="background-image: url({{ url_for('static', filename='assets/img/backgrounds/pattern_01.jpg')}})")
|
||||
.page-card-side
|
||||
|
||||
h2.page-card-title
|
||||
| All of this, plus hours of training and production assets.
|
||||
.page-card-summary
|
||||
|
||||
.page-card-summary.text-white
|
||||
| Join us for only $9.90/month!
|
||||
a.page-card-cta(
|
||||
href="https://store.blender.org/product/membership/")
|
||||
|
||||
a.page-card-cta(href="https://store.blender.org/product/membership/")
|
||||
| Subscribe Now
|
||||
| {% endif %}
|
||||
| {% endif %}
|
||||
|
||||
|
||||
| {% endblock %}
|
||||
|
||||
| {% block footer_scripts %}
|
||||
script.
|
||||
// Click anywhere in the page to hide the overlay
|
||||
// Hide the video overlay.
|
||||
function hideOverlay() {
|
||||
$('#page-overlay.video').removeClass('active');
|
||||
$('#page-overlay.video .video-embed').html('');
|
||||
}
|
||||
|
||||
// Click anywhere in the page or hit Esc to hide the overlay.
|
||||
$(document).click(function() {
|
||||
hideOverlay();
|
||||
});
|
||||
@@ -229,12 +217,12 @@ script.
|
||||
});
|
||||
|
||||
$('a.js-watch-video').click(function(e){
|
||||
var videoId = $(this).attr('data-youtube-id');
|
||||
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
$('#page-overlay.video').addClass('active');
|
||||
|
||||
var videoId = $(this).attr('data-youtube-id');
|
||||
$('#page-overlay .video-embed').html('<iframe src="https://www.youtube.com/embed/' + videoId +'?rel=0&showinfo=0;autoplay=1" frameborder="0" allowfullscreen></iframe>')
|
||||
});
|
||||
|
||||
|
@@ -53,7 +53,7 @@ h4 Thank you for supporting us!
|
||||
hr
|
||||
p Subscription expires on: <strong>{{ expiration_date }}</strong>
|
||||
p
|
||||
a(href="{{ config['EXTERNAL_SUBSCRIPTIONS_MANAGEMENT_SERVER'] | urljoin('my-account/subscriptions/') }}") Manage your subscription on Blender Store
|
||||
a(href="{{ config['EXTERNAL_SUBSCRIPTIONS_MANAGEMENT_SERVER'] | urljoin('/my-account/subscriptions/') }}") Manage your subscription on Blender Store
|
||||
|
||||
//---------------------------------
|
||||
| {% elif user_cls == 'subscriber-org' %}
|
||||
@@ -65,7 +65,7 @@ p Your organisation provides you with your subscription.
|
||||
|
||||
hr
|
||||
p
|
||||
button#recheck_subscription.btn.btn-default(onclick="javascript:recheck_subscription(this)") Re-check my subscription
|
||||
button#recheck_subscription.btn.btn-outline-secondary(onclick="javascript:recheck_subscription(this)") Re-check my subscription
|
||||
|
||||
hr
|
||||
|
||||
|
@@ -11,7 +11,7 @@
|
||||
| {% endfor %}
|
||||
|
||||
.buttons
|
||||
button.btn.btn-default.button-submit(type='submit')
|
||||
button.btn.btn-outline-success.button-submit(type='submit')
|
||||
i.pi-check
|
||||
| Save Changes
|
||||
| {% endblock %}
|
||||
|
@@ -19,18 +19,21 @@ meta(property="og:image", content="{{ url_for('static', filename='assets/img/bac
|
||||
|
||||
| {% block navigation_search %}{% endblock %}
|
||||
| {% block navigation_sections %}
|
||||
li
|
||||
a.navbar-item(href="{{ url_for('main.main_blog') }}")
|
||||
span Blog
|
||||
li
|
||||
a.navbar-item(href="#pricing")
|
||||
span Pricing
|
||||
| {% endblock navigation_sections %}
|
||||
|
||||
| {% block navigation_user %}
|
||||
li.nav-item-sign-in
|
||||
li.pt-1.pr-1
|
||||
| {% if current_user.is_anonymous %}
|
||||
a.navbar-item(href="{{ url_for('users.login', next='/') }}")
|
||||
a.btn.btn-outline-success(href="{{ url_for('users.login', next='/') }}")
|
||||
| Log in and Explore
|
||||
| {% else %}
|
||||
a.navbar-item(href="{{ url_for('main.homepage') }}")
|
||||
a.btn.btn-outline-success(href="{{ url_for('main.homepage') }}")
|
||||
| Explore
|
||||
| {% endif %}
|
||||
| {% endblock navigation_user %}
|
||||
@@ -113,21 +116,21 @@ li.nav-item-sign-in
|
||||
|
||||
section.page-card-header
|
||||
a(href="{{ url_for('cloud.courses') }}")
|
||||
h2 Featured Training
|
||||
h2 Featured Content
|
||||
|
||||
.page-triplet-container.homepage
|
||||
.row
|
||||
.col-md-4
|
||||
.triplet-card(data-url="https://cloud.blender.org/p/toon-character-workflow/")
|
||||
.triplet-card(data-url="https://cloud.blender.org/p/minecraft-animation-workshop/")
|
||||
.triplet-card-thumbnail
|
||||
img(
|
||||
alt="Textures",
|
||||
src="{{ url_for('static', filename='assets/img/features/training_toon_character.jpg')}}")
|
||||
src="{{ url_for('static', filename='assets/img/features/training_minecraft_animation.jpg')}}")
|
||||
.triplet-card-info
|
||||
h3 Toon Character Workflow
|
||||
h3 Minecraft Animation
|
||||
p.
|
||||
Perfect for beginners, learn how to build a cartoon character from concept to finish.
|
||||
a.triplet-cta(href="https://cloud.blender.org/p/toon-character-workflow/")
|
||||
Learn how to make animations with this workshop by Dillon Gu.
|
||||
a.triplet-cta(href="https://cloud.blender.org/p/minecraft-animation-workshop/")
|
||||
| LEARN MORE
|
||||
|
||||
.col-md-4
|
||||
@@ -160,6 +163,7 @@ li.nav-item-sign-in
|
||||
.col-md-10.col-md-offset-1
|
||||
p.
|
||||
Other training:
|
||||
#[a(href="https://cloud.blender.org/p/toon-character-workflow/") Toon Character Workflow],
|
||||
#[a(href="https://cloud.blender.org/p/3d-printing/") Blender for 3D Printing],
|
||||
#[a(href="https://cloud.blender.org/p/game-asset-creation/") Game Asset Creation],
|
||||
#[a(href="https://cloud.blender.org/p/blenderella/") Character Modeling],
|
||||
@@ -211,35 +215,41 @@ li.nav-item-sign-in
|
||||
.page-triplet-container.homepage
|
||||
.row
|
||||
.col-md-4
|
||||
.triplet-card(data-url="https://cloud.blender.org/p/cosmos-laundromat/")
|
||||
.triplet-card(data-url="https://cloud.blender.org/p/hero/")
|
||||
.triplet-card-thumbnail
|
||||
img(
|
||||
alt="HDRI",
|
||||
src="{{ url_for('static', filename='assets/img/features/open_movies_cosmos.jpg')}}")
|
||||
alt="Hero",
|
||||
src="{{ url_for('static', filename='assets/img/features/open_movies_hero.jpg')}}")
|
||||
.triplet-card-info
|
||||
h3 Cosmos Laundromat
|
||||
a.triplet-cta(href="https://cloud.blender.org/p/cosmos-laundromat/")
|
||||
h3 Hero
|
||||
p.
|
||||
The first ever Grease Pencil open movie made with Blender 2.8
|
||||
a.triplet-cta(href="https://cloud.blender.org/p/hero/")
|
||||
| LEARN MORE
|
||||
|
||||
.col-md-4
|
||||
.triplet-card(data-url="https://cloud.blender.org/p/agent-327/")
|
||||
.triplet-card(data-url="https://cloud.blender.org/p/spring/")
|
||||
.triplet-card-thumbnail
|
||||
img(
|
||||
alt="Textures",
|
||||
src="{{ url_for('static', filename='assets/img/features/open_movies_agent_barbershop.jpg')}}")
|
||||
alt="Spring",
|
||||
src="{{ url_for('static', filename='assets/img/features/open_movies_spring.jpg')}}")
|
||||
.triplet-card-info
|
||||
h3 Agent 327
|
||||
a.triplet-cta(href="https://cloud.blender.org/p/agent-327/")
|
||||
h3 Spring
|
||||
p.
|
||||
A poetic fantasy film. #[br] A stunning visual journey.
|
||||
a.triplet-cta(href="https://cloud.blender.org/p/spring/")
|
||||
| LEARN MORE
|
||||
|
||||
.col-md-4
|
||||
.triplet-card(data-url="https://cloud.blender.org/p/caminandes-3/")
|
||||
.triplet-card-thumbnail
|
||||
img(
|
||||
alt="Characters",
|
||||
alt="Caminandes",
|
||||
src="{{ url_for('static', filename='assets/img/features/open_movies_caminandes_llamigos.jpg')}}")
|
||||
.triplet-card-info
|
||||
h3 Caminandes
|
||||
p.
|
||||
Follow the adventures of Koro through the Patagonian pampas.
|
||||
a.triplet-cta(href="https://cloud.blender.org/p/caminandes-3/")
|
||||
| LEARN MORE
|
||||
|
||||
@@ -251,8 +261,10 @@ li.nav-item-sign-in
|
||||
#[a(href="https://cloud.blender.org/p/big-buck-bunny/") Big Buck Bunny],
|
||||
#[a(href="https://cloud.blender.org/p/sintel/") Sintel],
|
||||
#[a(href="https://cloud.blender.org/p/tears-of-steel/") Tears of Steel],
|
||||
#[a(href="https://cloud.blender.org/p/cosmos-laundromat/") Cosmos Laundromat],
|
||||
#[a(href="https://cloud.blender.org/p/glass-half/") Glass Half],
|
||||
#[a(href="https://cloud.blender.org/p/dailydweebs/") The Daily Dweebs]
|
||||
#[a(href="https://cloud.blender.org/p/dailydweebs/") The Daily Dweebs],
|
||||
#[a(href="https://cloud.blender.org/p/agent-327/") Agent 327]
|
||||
and #[a(href="{{ url_for('cloud.open_projects') }}") more]
|
||||
|
||||
|
||||
@@ -344,7 +356,7 @@ li.nav-item-sign-in
|
||||
.pricing-display
|
||||
span.currency-sign €
|
||||
span.digit-int 9
|
||||
span.digit-dec ,90 / month
|
||||
span.digit-dec ,90 / month
|
||||
|
||||
.pricing-caption
|
||||
p $11.50 USD
|
||||
@@ -359,7 +371,7 @@ li.nav-item-sign-in
|
||||
.pricing-display
|
||||
span.currency-sign €
|
||||
span.digit-int 109
|
||||
span.digit-dec ,00 / year
|
||||
span.digit-dec ,00 / year
|
||||
|
||||
.pricing-caption
|
||||
p $119 USD
|
||||
@@ -369,12 +381,12 @@ li.nav-item-sign-in
|
||||
|
||||
.box.monthly
|
||||
a(href="{{ subscribe_url }}")
|
||||
h3 Quaterly
|
||||
h3 Quarterly
|
||||
|
||||
.pricing-display
|
||||
span.currency-sign €
|
||||
span.digit-int 28
|
||||
span.digit-dec ,50 / year
|
||||
span.digit-dec ,50 / 3 months
|
||||
|
||||
.pricing-caption
|
||||
p $32 USD
|
||||
|
@@ -1,112 +0,0 @@
|
||||
/* cyrillic-ext */
|
||||
@font-face {
|
||||
font-family: 'Roboto';
|
||||
font-style: normal;
|
||||
font-weight: 300;
|
||||
src: local('Roboto Light'), local('Roboto-Light'), url(0eC6fl06luXEYWpBSJvXCBJtnKITppOI_IvcXXDNrsc.woff2) format('woff2');
|
||||
unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F;
|
||||
}
|
||||
/* cyrillic */
|
||||
@font-face {
|
||||
font-family: 'Roboto';
|
||||
font-style: normal;
|
||||
font-weight: 300;
|
||||
src: local('Roboto Light'), local('Roboto-Light'), url(Fl4y0QdOxyyTHEGMXX8kcRJtnKITppOI_IvcXXDNrsc.woff2) format('woff2');
|
||||
unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
|
||||
}
|
||||
/* greek-ext */
|
||||
@font-face {
|
||||
font-family: 'Roboto';
|
||||
font-style: normal;
|
||||
font-weight: 300;
|
||||
src: local('Roboto Light'), local('Roboto-Light'), url(-L14Jk06m6pUHB-5mXQQnRJtnKITppOI_IvcXXDNrsc.woff2) format('woff2');
|
||||
unicode-range: U+1F00-1FFF;
|
||||
}
|
||||
/* greek */
|
||||
@font-face {
|
||||
font-family: 'Roboto';
|
||||
font-style: normal;
|
||||
font-weight: 300;
|
||||
src: local('Roboto Light'), local('Roboto-Light'), url(I3S1wsgSg9YCurV6PUkTORJtnKITppOI_IvcXXDNrsc.woff2) format('woff2');
|
||||
unicode-range: U+0370-03FF;
|
||||
}
|
||||
/* vietnamese */
|
||||
@font-face {
|
||||
font-family: 'Roboto';
|
||||
font-style: normal;
|
||||
font-weight: 300;
|
||||
src: local('Roboto Light'), local('Roboto-Light'), url(NYDWBdD4gIq26G5XYbHsFBJtnKITppOI_IvcXXDNrsc.woff2) format('woff2');
|
||||
unicode-range: U+0102-0103, U+1EA0-1EF9, U+20AB;
|
||||
}
|
||||
/* latin-ext */
|
||||
@font-face {
|
||||
font-family: 'Roboto';
|
||||
font-style: normal;
|
||||
font-weight: 300;
|
||||
src: local('Roboto Light'), local('Roboto-Light'), url(Pru33qjShpZSmG3z6VYwnRJtnKITppOI_IvcXXDNrsc.woff2) format('woff2');
|
||||
unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;
|
||||
}
|
||||
/* latin */
|
||||
@font-face {
|
||||
font-family: 'Roboto';
|
||||
font-style: normal;
|
||||
font-weight: 300;
|
||||
src: local('Roboto Light'), local('Roboto-Light'), url(Hgo13k-tfSpn0qi1SFdUfVtXRa8TVwTICgirnJhmVJw.woff2) format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215;
|
||||
}
|
||||
/* cyrillic-ext */
|
||||
@font-face {
|
||||
font-family: 'Roboto';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: local('Roboto'), local('Roboto-Regular'), url(ek4gzZ-GeXAPcSbHtCeQI_esZW2xOQ-xsNqO47m55DA.woff2) format('woff2');
|
||||
unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F;
|
||||
}
|
||||
/* cyrillic */
|
||||
@font-face {
|
||||
font-family: 'Roboto';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: local('Roboto'), local('Roboto-Regular'), url(mErvLBYg_cXG3rLvUsKT_fesZW2xOQ-xsNqO47m55DA.woff2) format('woff2');
|
||||
unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
|
||||
}
|
||||
/* greek-ext */
|
||||
@font-face {
|
||||
font-family: 'Roboto';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: local('Roboto'), local('Roboto-Regular'), url(-2n2p-_Y08sg57CNWQfKNvesZW2xOQ-xsNqO47m55DA.woff2) format('woff2');
|
||||
unicode-range: U+1F00-1FFF;
|
||||
}
|
||||
/* greek */
|
||||
@font-face {
|
||||
font-family: 'Roboto';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: local('Roboto'), local('Roboto-Regular'), url(u0TOpm082MNkS5K0Q4rhqvesZW2xOQ-xsNqO47m55DA.woff2) format('woff2');
|
||||
unicode-range: U+0370-03FF;
|
||||
}
|
||||
/* vietnamese */
|
||||
@font-face {
|
||||
font-family: 'Roboto';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: local('Roboto'), local('Roboto-Regular'), url(NdF9MtnOpLzo-noMoG0miPesZW2xOQ-xsNqO47m55DA.woff2) format('woff2');
|
||||
unicode-range: U+0102-0103, U+1EA0-1EF9, U+20AB;
|
||||
}
|
||||
/* latin-ext */
|
||||
@font-face {
|
||||
font-family: 'Roboto';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: local('Roboto'), local('Roboto-Regular'), url(Fcx7Wwv8OzT71A3E1XOAjvesZW2xOQ-xsNqO47m55DA.woff2) format('woff2');
|
||||
unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;
|
||||
}
|
||||
/* latin */
|
||||
@font-face {
|
||||
font-family: 'Roboto';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: local('Roboto'), local('Roboto-Regular'), url(CWB0XYA8bzo0kSThX0UTuA.woff2) format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215;
|
||||
}
|
BIN
static/assets/img/features/open_movies_hero.jpg
Normal file
After Width: | Height: | Size: 74 KiB |
BIN
static/assets/img/features/open_movies_spring.jpg
Normal file
After Width: | Height: | Size: 92 KiB |
BIN
static/assets/img/features/training_minecraft_animation.jpg
Normal file
After Width: | Height: | Size: 80 KiB |
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 6.3 KiB |
Before Width: | Height: | Size: 43 KiB After Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 27 KiB |
Before Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 51 KiB After Width: | Height: | Size: 58 KiB |