168 Commits

Author SHA1 Message Date
b165c11cc3 Disable the flamenco-resume-job-archiving celery task until we have tested the job manually first 2018-09-19 17:35:12 +02:00
22bd1a1a04 Added celery task for resuming archiving flamenco jobs that got stuck in status "archiving".
Jobs are considered stuck if they are in status "archiving" and hasn't been updated in a day.
2018-09-19 14:02:28 +02:00
8e9d63df2b Follow art direction for Spring banner 2018-09-19 12:42:12 +02:00
10addb1521 Spring background for index collection 2018-09-19 12:38:35 +02:00
735e6400e3 Background for spring project 2018-09-19 12:38:35 +02:00
a1d84196cd Add additional dependencies to package.json 2018-09-19 12:00:37 +02:00
678a03dbf1 Update NODE_TAGS 2018-09-19 11:34:22 +02:00
811dc4d65b Mark Production Lessons as new 2018-09-19 11:33:40 +02:00
265794d4b7 Pass title to /production 2018-09-19 11:20:32 +02:00
ece0ba4ae7 Dropdowns tweaks based on feedback 2018-09-19 11:20:17 +02:00
2395bd8090 Production Lessons: Added more tags 2018-09-18 16:54:57 +02:00
fef7d5feac Homepage: update image for Spring 2018-09-18 13:57:44 +02:00
f0f96bf2f1 Home project: Fix creating new projects 2018-09-18 13:57:25 +02:00
e05a0c0e04 Tagged assets: Style 'load more items' button 2018-09-18 12:55:14 +02:00
dbba955afe Homepage: use asset list template for random assets 2018-09-18 12:54:57 +02:00
17240f5793 Landing: fix styling of gallery 2018-09-18 12:54:32 +02:00
8ff8975dbb Welcome page: Styling 2018-09-17 18:42:04 +02:00
0a144ec12d Blog: Edit post link 2018-09-17 18:34:43 +02:00
6d9fa89d90 Project: Darker tree 2018-09-17 18:15:49 +02:00
06e7ea53bb Footer: Fix broken links 2018-09-17 18:15:36 +02:00
00cd29befc Layout: move footer and main menu into their own files 2018-09-17 17:18:43 +02:00
a5c7ec285d Style tweaks 2018-09-17 17:09:43 +02:00
7fff47c5c5 Use spans for index_collection navigation 2018-09-17 15:04:07 +02:00
534e212802 Project Landing: Don't set title
As it's set by the pages themselves using node.properties.url
2018-09-17 15:03:53 +02:00
1fac97e3f8 Homepage: style sidebar and cleanup CSS
homepage.sass is like 10 lines now :)
2018-09-17 12:52:01 +02:00
0556c5ae9a Homepage: Style comments 2018-09-17 12:16:52 +02:00
bb2c351460 Generic classes for styling 2018-09-17 11:36:57 +02:00
a65d771bd6 Tagged Assets: Support passing arguments
Pass LOAD_INITIAL_COUNT and LOAD_NEXT_COUNT

Also only show 'Load more' if LOAD_NEXT_COUNT is not set to 0
2018-09-16 06:30:48 +02:00
b50a3e1fb3 Tagged assets: add video progress and watched label 2018-09-16 05:52:20 +02:00
6f88de3b20 Menu: Remove columns for trainings
Experiment with a more compact menu, more readable with not so much text.
2018-09-16 05:06:41 +02:00
6569e22fa8 Spacing 2018-09-16 05:03:12 +02:00
c773145bd6 Blog: use jumbotron overlay 2018-09-16 04:28:37 +02:00
ae907719d0 Index Collection: Limit columns to 3 2018-09-16 03:43:21 +02:00
88f936772d Blog: Layout adjustments 2018-09-16 03:06:08 +02:00
0f1088702d Blog: Fix showing wrong single post
Also center comments and other minor tweaks
2018-09-16 02:03:25 +02:00
40f6ebd99c Project Landing: Fix links in latest updates
Part of T56813
2018-09-15 22:19:47 +02:00
fca2b0f44f Project Landing: Center titles
Part of T56813
2018-09-15 22:15:05 +02:00
08b1b03802 Blog: name in title 2018-09-15 22:09:12 +02:00
23bf27ca75 Layout: Add Art Gallery to menu 2018-09-15 21:36:32 +02:00
15264877e6 Blog minor fixes and tweaks 2018-09-15 21:33:11 +02:00
2eb969f7ee Blog listing: Show posts as cards 2018-09-15 21:23:45 +02:00
1196f178e8 One class too much 2018-09-15 17:26:56 +02:00
aaeecc1429 Profile page: Styling and layout 2018-09-15 16:41:47 +02:00
df33a1803e Navigation menu: re-order items and minor tweaks 2018-09-15 06:15:49 +02:00
dc59bb53de New global navigation menu. 2018-09-15 05:36:23 +02:00
8fdd54eaad Class names and minor blog style tweak 2018-09-14 20:30:58 +02:00
11f44560bb Projects: Use render_secondary_navigation macro 2018-09-14 17:13:54 +02:00
1015254d93 Bring project-main from Pillar
Rename project-landing, to _project-landing and include it in project-main.
It's just a few lines of code to be worth keeping as a separate CSS.
2018-09-14 17:13:23 +02:00
dfa0c14bb0 Fix Scss paths 2018-09-14 14:43:55 +02:00
bbb643e371 Update package-lock.json 2018-09-14 13:12:07 +02:00
7d3c24d712 Project view: update videojs path
The new path removes the version number, check package.json for that.
2018-09-14 01:25:50 +02:00
e1433c3c2a Layout: Cleanup
* jQuery and Bootstrap are now part of tutti, built by Pillar.
* Markdown is no longer used as Pillar has its own converter.
2018-09-14 01:21:22 +02:00
90d6685add Gulp: Cleanup dead code
The task for building tutti was never used, since all functionality is
built by pillar.

Also remove the dependencies for jQuery and Bootstrap in package.json
2018-09-14 01:19:45 +02:00
9fd233a8dc Initial styling of production lessons. 2018-09-13 18:10:12 +02:00
a40eb5d6e4 Use the new navigation_links provided by pillar 2018-09-13 16:36:41 +02:00
fab0d412fa Navbar: padding for items 2018-09-12 19:00:54 +02:00
c5287da78c Layout: Search icon in the navbar 2018-09-11 19:41:16 +02:00
37726bee0f Cleanup 2018-09-11 19:35:05 +02:00
10f15185e0 Homepage: Use macro for listing assets 2018-09-11 17:46:09 +02:00
c90cd41e23 Use list-asset() mixin for homepage latest assets 2018-09-10 19:01:43 +02:00
c8261e5df6 User menu tweaks 2018-09-10 17:12:34 +02:00
34ae8e55c3 Project view: show blog and pages in header 2018-09-10 16:12:09 +02:00
f9368c0729 Layout: Use mixin for top navigation 2018-09-07 18:12:01 +02:00
b4c51007ab Style production listing 2018-09-07 18:11:46 +02:00
a17253c482 Project Landing: Fix missing pillar-font 2018-09-07 18:11:37 +02:00
e348b003b1 Use mixin components from Pillar 2018-09-07 17:19:36 +02:00
23fbb68cfc Replace #project-loading spinning icon with a .loader-bar 2018-09-07 14:56:38 +02:00
4a4e75ee59 Project view: Cleanup and minor layout tweaks 2018-09-07 12:47:31 +02:00
9aae856ac8 Project View: fix alignment of edit tools 2018-09-06 18:16:07 +02:00
94c2c6e550 Start of "production videos", a.k.a. tagged assets overview
Tagged assets are shown in a list per tag. The list is dynamically
loaded with JavaScript.
2018-09-06 16:08:23 +02:00
0b1f295480 Services: Fix layout 2018-09-06 14:33:44 +02:00
a64d3902fd Bootstrap popovers are no longer used. 2018-09-06 14:24:31 +02:00
8dd1de1018 Merge branch 'wip-redesign'
# Conflicts:
#	src/templates/homepage.pug
#	src/templates/services.pug
2018-09-06 14:13:22 +02:00
b12b320cf0 Project view: include video_plugins.min.js 2018-09-06 13:32:57 +02:00
95e51e90de Homepage cleanup. 2018-09-06 13:03:40 +02:00
92106459a0 Use Navigation Tabs for homepage and index collections 2018-09-06 13:03:22 +02:00
982047fc3b Pug: Tweaks to components 2018-09-06 12:59:48 +02:00
5a36888f61 Navigation: Dropdowns for Services and Open Projects 2018-09-06 12:58:56 +02:00
59e0adf3ca Pug: Bring new templates from Pillar 2018-09-06 12:54:15 +02:00
3fdbb92b93 Fixed typo 2018-09-05 15:11:46 +02:00
cf98883633 Make <meta property="og:url"> tags have an absolute URL
Most of those can simply use `{{ request.url }}`, as this already contains
the absolute URL of the currently displayed page.
2018-09-05 13:58:13 +02:00
7b32b97203 Remove https://cloud.blender.org from URLs
URLs should be host-relative, so that they also work on devservers.
URLs in emails should remain absolute, though; we may want to change those
to use {{ url_for(..., _external=True) }} at some point.
2018-09-05 13:57:15 +02:00
7f58be4568 Updated Blender Cloud add-on to 1.9.0
Also change the config_local.py so that we only have to change one variable
for a new version.
2018-09-05 13:40:24 +02:00
c25df6f0ad Cleanup 2018-08-31 19:33:14 +02:00
813750a006 Layout cleanup 2018-08-31 19:08:52 +02:00
9ba2735c8c CSS Cleanup 2018-08-31 19:08:23 +02:00
73e8a81f3c Minor style tweaks 2018-08-31 13:58:26 +02:00
9ffcde3348 Use styling from Pillar 2018-08-31 13:58:08 +02:00
f0b18e88f4 Use pug mixins for header, cards and navigation 2018-08-31 13:57:51 +02:00
ed211f9473 Introducing Pug mixin components
For now added jumbotron, secondary navigation, card decks and individual cards.

Thanks @sybren for the suggestion.
2018-08-31 13:57:09 +02:00
7a4c7d75f6 Project Landing uses new CSS 2018-08-31 13:55:35 +02:00
cc16351136 NPM: Upgrade libraries
New dependency is jQuery, we already depended on it it but now we use it
from npm package for easier version control and upgrades.
2018-08-31 13:54:39 +02:00
7dc1e6f9a1 Gulp: Only chmod files when in --production 2018-08-31 13:53:37 +02:00
fcf715b5b1 Services page: move to bootstrap 4 2018-08-31 13:52:40 +02:00
099984f97c Added #!/bin/sh at top of shell script 2018-08-30 12:53:34 +02:00
8bfb40ce54 Various Docker image upgrades, read the entire commit message!
- Ubuntu 17.10 → 18.04.
- Python 3.6.3 → 3.6.6.
- Use `DEBIAN_FRONTEND=noninteractive` to prevent prompts during
  installation.
- Install `tzdata` in the base image as it's required by subimages.
- Correctly set maintainer in Dockerfile.
2018-08-30 12:53:34 +02:00
d60a65c9f0 End BLENDER_ID_ENDPOINT with a slash 2018-08-30 12:46:58 +02:00
9cd2853e49 Upgrade pip after building Python 2018-08-30 12:31:31 +02:00
3d5554d9ce BLENDER_ID_ENDPOINT should end with a slash
There is always a path component in a URL.
2018-08-29 14:20:23 +02:00
764ccfa78e Add -e . to requirements-dev.txt 2018-08-29 12:24:45 +02:00
0b8ebecfea Don't import flask_login.request
`flask_login.request` is the exact same thing as `flask.request`, so
importing it from `flask_login` makes no sense. Also, it's been removed
from the new Flask-Login.
2018-08-29 12:19:40 +02:00
68d09dc886 CSS: cleanup 2018-08-28 15:56:06 +02:00
169a7f51f0 navbar-container: cleanup 2018-08-28 15:55:32 +02:00
f48a4883ae Index collection redesign 2018-08-27 16:58:01 +02:00
01b6693324 Cleanup styling. Use bootstrap classes instead 2018-08-27 16:57:34 +02:00
012ba06655 Use system fonts (see main.sass) 2018-08-27 16:55:48 +02:00
2be601d0b0 Introducing Bootstrap 4
Bootstrap (and its dependency popper.js) are now used from npm packages,
allowing better version control and custom building of only the required
components for both styling and javascript.

At this moment the whole styling of bootstrap is included, once the Cloud
redesign is over it will be stripped to only the used components.
2018-08-27 16:52:22 +02:00
4dc11b075a Gulp: watch Pillar styles folder for changes and compile Sass 2018-08-27 15:17:00 +02:00
4696d09fed Corrected rewrite rule for Caminandes 2018-07-06 14:56:40 +02:00
663cf7bf2d Update README with further setup instructions 2018-06-25 18:58:59 +02:00
4e8530478a Remove trailing slash from BLENDER_ID_ENDPOINT 2018-06-22 19:40:44 +02:00
b2d10b5ca7 Expose BLENDER_ID_ENDPOINT in example config 2018-06-22 19:40:10 +02:00
fad1aa75e9 Add virtualenv creation instructions 2018-06-22 16:23:44 +02:00
d9cf2d8631 Include package-lock.json for this project 2018-06-22 16:14:28 +02:00
34cb45d5f1 Fix link to Pillar website in README.md 2018-06-22 16:13:56 +02:00
3c74e79e4a Update README.md featuring Development setup instructions 2018-06-22 16:09:29 +02:00
fb2f245f1e Introducing "./gulp all" all command
It automates running the ./gulp command in all blender-cloud
related repos. Useful when setting up a project for the first time.
2018-06-22 15:39:24 +02:00
8c6fa1e423 Add config_local.example.py for development setup. 2018-06-21 19:08:07 +02:00
b66b6cf445 Reduced verbosity of mongo-backup.sh 2018-06-14 11:57:36 +02:00
b153cae70e Increased WSGI thread count 32 → 64 2018-06-13 10:47:28 +02:00
368e3f6d40 Use high-res image for landing header 2018-05-07 15:25:10 +02:00
e9ec850d90 Styling: tweaks for mobile 2018-04-17 00:28:49 +02:00
280d26801e Reduce scope of h2 styling 2018-04-16 20:36:23 +02:00
6c285c078f Cleanup for landing page 2018-04-16 18:35:16 +02:00
debfc4e356 Layout tweaks for landing page 2018-04-16 17:30:34 +02:00
541663ce0c Skip nodes with no image in landing gallery 2018-04-16 16:36:03 +02:00
1fb044e7c1 Use macro for rendering secondary navigation 2018-04-16 16:24:00 +02:00
4769e2e904 New landing page for Hero project 2018-04-16 14:38:08 +02:00
cf99383b9c Only load clipboard.min.js when authenticated
This is used in the attachments form, which is only available to
authenticated users.
2018-04-03 11:28:13 +02:00
8613ac7244 Merge branch 'master' into production 2018-04-03 11:10:01 +02:00
8c48a61114 Switched attachment rendering to shortcode system
Requires Pillar 3b452d14ce32e1a744fc526a922e1bb60b83ef25 or newer.
2018-04-03 11:02:33 +02:00
0259c5e0ec Add clipboard.min.js to layout
This is needed for the copy button on file asset uploading. Rather than
including it on all pages that could feature the file uploader it's now
loaded globally.
2018-03-29 17:34:22 +02:00
a726fd1fbe Remove time from logs; timestamp is added by Apache anyway. 2018-03-29 11:01:51 +02:00
df71738623 Nodes preview now uses typewatch and csrf_token 2018-03-28 23:38:09 +02:00
6a698daaa0 Remove time from logs; timestamp is added by Apache anyway. 2018-03-27 16:42:43 +02:00
7f538bdaee Replace "Intel" with "Intel Software" logo 2018-03-27 12:12:26 +02:00
5f07c7ce17 Use new hashing of static file names.
Every time the docker image is rebuilt a random hash is chosen.

Requires Pillar d560f89704e3a6f4490df57712525048c469bed2 or newer.
2018-03-23 17:39:04 +01:00
5a42e2dcb8 Flip condition to unindent pretty much all the code
No semantic changes.
2018-03-23 17:24:36 +01:00
d5a54b7cf1 Formatting 2018-03-23 16:37:58 +01:00
7cb4b37ae2 Renamed some docker files to Dockerfile
This makes it simpler to manage by using the default name. It also helps
my editor to recognise the file and highlight it properly.
2018-03-23 12:42:56 +01:00
1fca473257 Moved Apache files into separate subdir 2018-03-23 12:12:39 +01:00
5a6035a494 Change hostname from blender-cloud → cloud.local in docker-compose.yml 2018-03-23 12:11:59 +01:00
98698be7eb Add redirect for waking-the-forest project
Waking the Forest was originally part of the Art Gallery, but was
moved to its own dedicated workshop to increase visibility.
2018-03-19 11:17:45 +01:00
d4f072480c Add convenience redirect from /hero to /p/hero 2018-03-14 14:35:27 +01:00
2d036ee657 Fix rsync of MongoDB backup to Swami
- Forcing IPv4 no longer necessary
- Directory on Swami is determined by rrsync parameters on Swami side
  .ssh/authorized_keys file.
2018-03-09 14:02:39 +01:00
1bb762db6b Document simplified deployment procedure 2018-03-07 15:44:13 +01:00
11743c54e2 Fixed quarterly pricing + all pricing layout tweak 2018-03-07 15:12:36 +01:00
29d1d02bfd Prevent error when there are no Mongo backups to remove yet. 2018-02-22 10:01:59 +01:00
f7e5db2174 Copy files from work directory instead of $DOCKER_DEPLOYDIR
Those files aren't run from inside docker, so it's unnecessary to rebuild
the docker image when we want to deploy new versions.
2018-02-21 11:30:48 +01:00
2141aed06c Added script to run on server for nightly MongoDB backups
Forced to use IPv4 due to IPv6 connectivity issues with Swami.
2018-02-21 11:30:48 +01:00
6b56df9e9c Welcome page additions
New projects: Hero, Spring, and Minecraft Animation Workshop!
2018-02-20 18:56:18 +01:00
5a4519659a Welcome page: typo in Quarterly
Thanks to Michael Glass @WebSmith for reporting!
2018-02-14 12:06:20 +01:00
c3ddc831aa Stricter XSendFilePath in Apache config 2018-02-14 11:07:47 +01:00
1a63b51c48 Ignore files created by running celery-beat outside docker 2018-02-13 12:33:24 +01:00
484ac34c50 T53983: Explicitly version the picohttp docker image 2018-02-13 12:32:19 +01:00
908360eb1c Scripts for easier deployment without leaving ./deploy/
You can choose between build-quick.sh (which only does 4_run/build.sh) and
build-all.sh (which does a full Docker image rebuild).
2018-02-13 11:46:29 +01:00
3bf1c3ea1b About: introducing team profiles
CSS should be refactored, probably in the page itself.
2018-02-12 00:32:12 +01:00
fc58fbef5b Resize and cleanup team profile pictures 2018-02-12 00:29:22 +01:00
9e961580d3 Fix bad URL to store 2018-02-06 10:59:23 +01:00
87cf5a9844 Revert "Add https://cloud*/* as virtual host to haproxy config"
This reverts commit 3be926b9b3.
2018-02-02 13:00:57 +01:00
5a3a7a3883 LetsEncrypt fixes
- Changed virtual host weight for the letsencrypt docker so that it is
  higher than any other weight
- Copy the renewal script to the server (previously it was available
  to the host at /data/git/blender-cloud/…, but no longer.
2018-02-02 12:39:07 +01:00
3be926b9b3 Add https://cloud*/* as virtual host to haproxy config
This allows testing on https://cloud3/ for example, without having to
edit the docker-compose.yml file on the cloud3 server.
2018-02-02 12:20:39 +01:00
ff5af22771 Added missing hostname in README 2018-02-02 12:20:00 +01:00
6f73222dcd Add the most-changing files as last step for faster Docker rebuilds. 2018-02-02 12:09:16 +01:00
1617a119db No more branch check for subprojects
We only need the locations of those subprojects to get their Git URL, and
the state of the work directory doesn't matter.
2018-02-02 12:04:47 +01:00
bef402a6b0 Updated documentation for the new way to deploy Blender Cloud 2018-02-02 12:01:47 +01:00
94ef616593 Placing code + assets directly into Docker image
This radically changes the way we deploy to the production server, as a
Git checkout is no longer required there. All the necessary files are
now inside the docker image. As a result, /data/git should no longer be
mounted as a Docker volume.

- Renamed docker/build.sh → docker/full_rebuild.sh
  This makes it clearer that it performs a full rebuild of the Docker images.
- Full rebuilds should be done on a regular basis to pull in Ubuntu
  security updates.
- Removed rsync_ui.sh, we no longer need it. Other projects can also
  remove their rsync_ui.sh.
- Moved deploy.sh → deploy/2docker.sh and added deploy/2server.sh
2018-02-02 12:01:17 +01:00
ffc4f271e8 Upgrade Docker base image to Ubuntu 17.10 2018-02-01 15:53:33 +01:00
99 changed files with 9859 additions and 2179 deletions

8
.gitignore vendored
View File

@@ -3,9 +3,11 @@
.coverage
*.pyc
__pycache__
*.js.map
*.css.map
/cloud/templates/
/cloud/static/assets/css/
/cloud/static/assets/
node_modules/
/config_local.py
@@ -18,3 +20,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
View File

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

View File

@@ -41,6 +41,8 @@ class CloudExtension(PillarExtension):
'EXTERNAL_SUBSCRIPTIONS_MANAGEMENT_SERVER': 'https://store.blender.org/api/',
'EXTERNAL_SUBSCRIPTIONS_TIMEOUT_SECS': 10,
'BLENDER_ID_WEBHOOK_USER_CHANGED_SECRET': 'oos9wah1Zoa0Yau6ahThohleiChephoi',
'NODE_TAGS': ['animation', 'modeling', 'rigging', 'sculpting', 'shading', 'texturing', 'lighting',
'character-pipeline', 'effects', 'video-editing'],
}
def eve_settings(self):

View File

@@ -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)
@@ -398,6 +391,13 @@ def privacy():
return render_template('privacy.html')
@blueprint.route('/production')
def production():
return render_template(
'production.html',
title='production')
@blueprint.route('/emails/welcome.send')
@login_required
def emails_welcome_send():
@@ -443,6 +443,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)

3
cloud/tagged/__init__.py Normal file
View File

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

16
cloud/tagged/routes.py Normal file
View File

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

View File

@@ -7,8 +7,7 @@ import json
import logging
import typing
from flask_login import request
from flask import Blueprint
from flask import Blueprint, request
import werkzeug.exceptions as wz_exceptions
from pillar import current_app

32
config_local.example.py Normal file
View 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
View File

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

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

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

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

10
docker/1_base/Dockerfile Normal file
View File

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

View File

@@ -1,6 +0,0 @@
FROM ubuntu:16.10
MAINTAINER Francesco Siddi <francesco@blender.org>
RUN apt-get update && apt-get install -qyy \
-o APT::Install-Recommends=false -o APT::Install-Suggests=false \
openssl ca-certificates

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

64
docker/4_run/Dockerfile Executable file
View File

@@ -0,0 +1,64 @@
FROM armadillica/pillar_py:3.6
LABEL maintainer="Sybren A. Stüvel <sybren@blender.studio>"
RUN set -ex; \
apt-get update; \
DEBIAN_FRONTEND=noninteractive apt-get install -qy \
-o APT::Install-Recommends=false -o APT::Install-Suggests=false \
git \
apache2 \
libapache2-mod-xsendfile \
libjpeg8 \
libtiff5 \
ffmpeg \
rsyslog logrotate \
nano vim-tiny curl; \
rm -rf /var/lib/apt/lists/*
RUN ln -s /usr/bin/vim.tiny /usr/bin/vim
ENV APACHE_RUN_USER www-data
ENV APACHE_RUN_GROUP www-data
ENV APACHE_LOG_DIR /var/log/apache2
ENV APACHE_PID_FILE /var/run/apache2.pid
ENV APACHE_RUN_DIR /var/run/apache2
ENV APACHE_LOCK_DIR /var/lock/apache2
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/config
VOLUME /data/storage
VOLUME /var/log
ENV USE_X_SENDFILE True
EXPOSE 80
EXPOSE 5000
ADD apache/wsgi-py36.* /etc/apache2/mods-available/
RUN a2enmod rewrite && a2enmod wsgi-py36
ADD apache/apache2.conf /etc/apache2/apache2.conf
ADD apache/000-default.conf /etc/apache2/sites-available/000-default.conf
ADD apache/logrotate.conf /etc/logrotate.d/apache2
ADD *.sh /
# Remove some empty top-level directories we won't use anyway.
RUN rmdir /media /home 2>/dev/null || true
# This file includes some useful commands to have in the shell history
# for easy access.
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)"

View File

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

View File

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

View File

@@ -0,0 +1,136 @@
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',
],
}
}
# Latest version of the add-on.
BLENDER_CLOUD_ADDON_VERSION = '1.9.0'
REDIRECTS = {
# For old links, refer to the services page (hopefully it refreshes then)
'downloads/blender_cloud-latest-bundle.zip': 'https://cloud.blender.org/services#blender-addon',
# Latest Blender Cloud add-on.
'downloads/blender_cloud-latest-addon.zip':
f'https://storage.googleapis.com/institute-storage/addons/'
f'blender_cloud-{BLENDER_CLOUD_ADDON_VERSION}.addon.zip',
# Redirect old Grafista endpoint to /stats
'/stats/': '/stats',
}
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)
},
# 'flamenco-resume-job-archiving': {
# 'task': 'flamenco.celery.job_archival.resume_job_archiving',
# 'schedule': 3600, # every N seconds
# },
}
SVNMAN_REPO_URL = 'https://svn.blender.cloud/repo/'
SVNMAN_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.

View File

@@ -1,18 +1,21 @@
#!/bin/sh
if [ ! -f /installed ]; then
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."
touch /installed
if [ -f /installed ]; then
return
fi
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."
touch /installed

View File

@@ -1,55 +0,0 @@
FROM armadillica/pillar_py:3.6
LABEL maintainer Sybren A. Stüvel <sybren@blender.studio>
RUN apt-get update && apt-get install -qyy \
-o APT::Install-Recommends=false -o APT::Install-Suggests=false \
git \
apache2 \
libapache2-mod-xsendfile \
libjpeg8 \
libtiff5 \
ffmpeg \
rsyslog logrotate \
nano vim-tiny curl \
&& rm -rf /var/lib/apt/lists/*
RUN ln -s /usr/bin/vim.tiny /usr/bin/vim
ENV APACHE_RUN_USER www-data
ENV APACHE_RUN_GROUP www-data
ENV APACHE_LOG_DIR /var/log/apache2
ENV APACHE_PID_FILE /var/run/apache2.pid
ENV APACHE_RUN_DIR /var/run/apache2
ENV APACHE_LOCK_DIR /var/lock/apache2
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
ENV USE_X_SENDFILE True
EXPOSE 80
EXPOSE 5000
ADD 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 *.sh /
# Remove some empty top-level directories we won't use anyway.
RUN rmdir /media /home 2>/dev/null || true
# This file includes some useful commands to have in the shell history
# for easy access.
ADD bash_history /root/.bash_history
ENTRYPOINT /docker-entrypoint.sh

View File

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

View File

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

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

View File

@@ -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,16 +12,16 @@ 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,
maps: argv.production,
maps: !argv.production,
failCheck: !argv.production,
prettyPug: !argv.production,
cachify: !argv.production,
cleanup: argv.production,
chmod: argv.production,
};
var destination = {
@@ -29,6 +30,10 @@ var destination = {
js: 'cloud/static/assets/js',
}
var source = {
pillar: '../pillar/'
}
/* CSS */
gulp.task('styles', function() {
@@ -64,6 +69,9 @@ gulp.task('templates', function() {
});
/* Tutti gets built by Pillar. See gulpfile.js in pillar.*/
/* Individual Uglified Scripts */
gulp.task('scripts', function() {
gulp.src('src/scripts/*.js')
@@ -73,28 +81,12 @@ 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()));
});
/* Collection of scripts in src/scripts/tutti/ to merge into tutti.min.js */
/* Since it's always loaded, it's only for functions that we want site-wide */
gulp.task('scripts_concat_tutti', function() {
gulp.src('src/scripts/tutti/**/*.js')
.pipe(gulpif(enabled.failCheck, plumber()))
.pipe(gulpif(enabled.maps, sourcemaps.init()))
.pipe(concat("tutti.min.js"))
.pipe(gulpif(enabled.uglify, uglify()))
.pipe(gulpif(enabled.maps, sourcemaps.write(".")))
.pipe(chmod(644))
.pipe(gulp.dest(destination.js))
.pipe(gulpif(argv.livereload, livereload()));
});
// While developing, run 'gulp watch'
gulp.task('watch',function() {
// Only listen for live reloads if ran with --livereload
@@ -103,11 +95,12 @@ gulp.task('watch',function() {
}
gulp.watch('src/styles/**/*.sass',['styles']);
gulp.watch('src/templates/**/*.pug',['templates']);
gulp.watch(source.pillar + 'src/styles/**/*.sass',['styles']);
gulp.watch('src/scripts/*.js',['scripts']);
gulp.watch('src/scripts/tutti/**/*.js',['scripts_concat_tutti']);
gulp.watch('src/templates/**/*.pug',['templates']);
});
// Erases all generated files in output directories.
gulp.task('cleanup', function() {
var paths = [];
@@ -125,4 +118,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']));

5779
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -8,19 +8,25 @@
},
"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",
"video.js": "^7.2.2"
}
}

View File

@@ -9,3 +9,4 @@
-e ../attract
-e ../flamenco
-e ../pillar-svnman
-e .

View File

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

View File

@@ -0,0 +1,145 @@
/**
* Support for fetching & rendering assets by tags.
*/
(function($) {
/* How many nodes to load initially, and when clicked on the 'Load Next' link. */
const LOAD_INITIAL_COUNT = 5;
const LOAD_NEXT_COUNT = 3;
/* Renders a node as an asset card, returns a jQuery object. */
function renderAsset(node) {
let card = $('<a class="card asset card-image-fade pr-0 mx-0 mb-2">')
.addClass('js-tagged-asset')
.attr('href', '/nodes/' + node._id + '/redir')
.attr('title', node.name);
let thumbnail_container = $('<div class="embed-responsive embed-responsive-16by9">');
function warnNoPicture() {
let card_icon = $('<div class="card-img-top card-icon embed-responsive-item">');
card_icon.html('<i class="pi-' + node.node_type + '">');
thumbnail_container.append(card_icon);
}
if (!node.picture) {
warnNoPicture();
} else {
// TODO: show 'loading' thingy
$.get('/api/files/' + node.picture)
.fail(function(error) {
let msg = xhrErrorResponseMessage(error);
console.log(msg);
})
.done(function(resp) {
// Render the picture if it has the proper size.
var show_variation = null;
if (typeof resp.variations != 'undefined') {
for (variation of resp.variations) {
if (variation.size != 'm') continue;
show_variation = variation;
break;
}
}
if (show_variation == null) {
warnNoPicture();
return;
}
let img = $('<img class="card-img-top embed-responsive-item">')
.attr('alt', node.name)
.attr('src', variation.link)
.attr('width', variation.width)
.attr('height', variation.height);
thumbnail_container.append(img);
});
}
card.append(thumbnail_container);
/* Card body for title and meta info. */
let card_body = $('<div class="card-body py-2 d-flex flex-column">');
let card_title = $('<div class="card-title mb-1 font-weight-bold">');
card_title.text(node.name);
card_body.append(card_title);
let card_meta = $('<ul class="card-text list-unstyled d-flex text-black-50 mt-auto">');
card_meta.append('<li>' + node._created + '</li>');
card_body.append(card_meta);
/* Video progress and 'watched' label. */
if (node.view_progress){
let card_progress = $('<div class="progress rounded-0">');
let card_progress_bar = $('<div class="progress-bar">');
card_progress_bar.css('width', node.view_progress.progress_in_percent);
card_progress.append(card_progress_bar);
card_body.append(card_progress);
if (node.view_progress.done){
let card_progress_done = $('<div class="card-label">WATCHED</div>');
card_body.append(card_progress_done);
}
}
/* 'Free' ribbon for public assets. */
if (node.permissions && node.permissions.world){
card.addClass('free');
}
card.append(card_body);
return card;
}
function loadNext(card_deck_element) {
let $card_deck = $(card_deck_element);
let tagged_assets = card_deck_element.tagged_assets; // Stored here by loadTaggedAssets().
let already_loaded = $card_deck.find('a.js-tagged-asset').length;
let load_next = $card_deck.find('a.js-load-next');
let nodes_to_load = tagged_assets.slice(already_loaded, already_loaded + LOAD_NEXT_COUNT);
for (node of nodes_to_load) {
let link = renderAsset(node);
load_next.before(link);
}
if (already_loaded + LOAD_NEXT_COUNT >= tagged_assets.length)
load_next.remove();
}
$.fn.loadTaggedAssets = function(LOAD_INITIAL_COUNT, LOAD_NEXT_COUNT) {
this.each(function(index, card_deck_element) {
// TODO(Sybren): show a 'loading' animation.
$.get('/api/nodes/tagged/' + card_deck_element.dataset.assetTag)
.fail(function(error) {
let msg = xhrErrorResponseMessage(error);
$('<a>').addClass('bg-danger').text(msg).appendTo(card_deck_element);
})
.done(function(resp) {
// 'resp' is a list of node documents.
// Store the response on the DOM card_deck_element so that we can later render more.
card_deck_element.tagged_assets = resp;
// Here render the first N.
for (node of resp.slice(0, LOAD_INITIAL_COUNT)) {
let li = renderAsset(node);
li.appendTo(card_deck_element);
}
// Don't bother with a 'load next' link if there is no more.
if (resp.length <= LOAD_INITIAL_COUNT) return;
if (LOAD_NEXT_COUNT > 0) {
// Construct the 'load next' link.
let link = $('<a class="btn btn-outline-primary px-5 mb-auto mx-3 btn-block">')
.addClass('js-load-next')
.attr('href', 'javascript:void(0);')
.click(function() { loadNext(card_deck_element); return false; })
.text('Load More');
link.appendTo(card_deck_element);
}
});
});
};
}(jQuery));

9
src/styles/_about.sass Normal file
View 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

View File

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

View File

@@ -0,0 +1,212 @@
$node-latest-thumbnail-size: 160px
$node-latest-gallery-thumbnail-size: 200px
.landing
.node-details-description
iframe
max-width: 100%
.node-extra
display: flex
flex-direction: column
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
.info
width: 100%
height: 100%
display: flex
flex-direction: column
justify-content: space-between
word-break: break-word
.description
font-size: 1em
line-height: 1.8em
padding-top: 8px
color: $color-text-dark-primary
.title
display: block
font-size: 1.3em
color: $color-text-dark
font-weight: 600
+clearfix
+text-overflow-ellipsis
span.details
width: 100%
display: block
font-size: 1em
line-height: 1.2em
padding: 5px 0
color: $color-text-dark-secondary
+clearfix
.who
margin-left: 3px
.what
text-transform: capitalize
$bg-color: #444
$bg-color2: #666
$yellow: rgb(249,229,89)
$almost-white: rgb(255,255,255)
$btn-transparent-color: rgba(249,229,89,1)
$btn-transparent-bg: rgba(249,229,89,0)
.gallery
max-width: 1024px
.thumbnail
float: left
position: relative
width: 23%
padding-bottom: 23%
margin: 0.83%
overflow: hidden
&:hover
box-shadow: 2px 2px 50px 0 rgba(0,0,0,0.3)
.img-container
position: absolute
width: 100%
height: 100%
img
width: 300%
transform: translate(-20%,-10%)
&:hover .img-caption
top: 0
left: 0
.btn-trans
background: rgba(255,255,255,0.4)
.img-caption
position: absolute
width: 100%
height: 100%
background: rgba(0, 0, 0, 0.3)
text-align: center
.table
display: table
.table-cell
display: table-cell
vertical-align: bottom
border: none
@media screen and (max-width: 992px)
.thumbnail
width: 22%
padding-bottom: 22%
margin: 1.5%
.img-container:hover .img-caption
top: 0
left: 0
.img-caption
position: absolute
width: 100%
height: 100%
background: rgba(0, 0, 0, .7)
text-align: center
a
color: $yellow
@media screen and (max-width: 720px)
.thumbnail
width: 29%
padding-bottom: 29%
margin: 2.16%
@media screen and (max-width: 470px)
.thumbnail
width: 44%
padding-bottom: 44%
margin: 3%

View File

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

View File

@@ -1,25 +1,98 @@
@import ../../../pillar/src/styles/_normalize
@import ../../../pillar/src/styles/_config
@import ../../../pillar/src/styles/_utils
// Bootstrap variables and utilities.
@import "../../../pillar/node_modules/bootstrap/scss/functions"
@import "../../../pillar/node_modules/bootstrap/scss/variables"
@import "../../../pillar/node_modules/bootstrap/scss/mixins"
/* 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 "../../../pillar/node_modules/bootstrap/scss/root"
@import "../../../pillar/node_modules/bootstrap/scss/reboot"
@import "../../../pillar/node_modules/bootstrap/scss/type"
@import "../../../pillar/node_modules/bootstrap/scss/images"
@import "../../../pillar/node_modules/bootstrap/scss/code"
@import "../../../pillar/node_modules/bootstrap/scss/grid"
@import "../../../pillar/node_modules/bootstrap/scss/tables"
@import "../../../pillar/node_modules/bootstrap/scss/forms"
@import "../../../pillar/node_modules/bootstrap/scss/buttons"
@import "../../../pillar/node_modules/bootstrap/scss/transitions"
@import "../../../pillar/node_modules/bootstrap/scss/dropdown"
@import "../../../pillar/node_modules/bootstrap/scss/button-group"
@import "../../../pillar/node_modules/bootstrap/scss/input-group"
@import "../../../pillar/node_modules/bootstrap/scss/custom-forms"
@import "../../../pillar/node_modules/bootstrap/scss/nav"
@import "../../../pillar/node_modules/bootstrap/scss/navbar"
@import "../../../pillar/node_modules/bootstrap/scss/card"
@import "../../../pillar/node_modules/bootstrap/scss/breadcrumb"
@import "../../../pillar/node_modules/bootstrap/scss/pagination"
@import "../../../pillar/node_modules/bootstrap/scss/badge"
@import "../../../pillar/node_modules/bootstrap/scss/jumbotron"
@import "../../../pillar/node_modules/bootstrap/scss/alert"
@import "../../../pillar/node_modules/bootstrap/scss/progress"
@import "../../../pillar/node_modules/bootstrap/scss/media"
@import "../../../pillar/node_modules/bootstrap/scss/list-group"
@import "../../../pillar/node_modules/bootstrap/scss/close"
@import "../../../pillar/node_modules/bootstrap/scss/modal"
@import "../../../pillar/node_modules/bootstrap/scss/tooltip"
@import "../../../pillar/node_modules/bootstrap/scss/popover"
@import "../../../pillar/node_modules/bootstrap/scss/carousel"
@import "../../../pillar/node_modules/bootstrap/scss/utilities"
@import "../../../pillar/node_modules/bootstrap/scss/print"
// Pillar components.
@import "../../../pillar/src/styles/apps_base"
@import "../../../pillar/src/styles/error"
@import "../../../pillar/src/styles/components/base"
@import "../../../pillar/src/styles/components/jumbotron"
@import "../../../pillar/src/styles/components/alerts"
@import "../../../pillar/src/styles/components/navbar"
@import "../../../pillar/src/styles/components/dropdown"
@import "../../../pillar/src/styles/components/footer"
@import "../../../pillar/src/styles/components/shortcode"
@import "../../../pillar/src/styles/components/statusbar"
@import "../../../pillar/src/styles/components/search"
@import "../../../pillar/src/styles/components/flyout"
@import "../../../pillar/src/styles/components/forms"
@import "../../../pillar/src/styles/components/inputs"
@import "../../../pillar/src/styles/components/buttons"
@import "../../../pillar/src/styles/components/popover"
@import "../../../pillar/src/styles/components/tooltip"
@import "../../../pillar/src/styles/components/checkbox"
@import "../../../pillar/src/styles/components/overlay"
@import "../../../pillar/src/styles/components/card"
@import "../../../pillar/src/styles/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 */

View File

@@ -0,0 +1,86 @@
// Bootstrap variables and utilities.
@import "../../../pillar/node_modules/bootstrap/scss/functions"
@import "../../../pillar/node_modules/bootstrap/scss/variables"
@import "../../../pillar/node_modules/bootstrap/scss/mixins"
// Pillar variables and utilities.
@import "../../../pillar/src/styles/_config"
@import "../../../pillar/src/styles/_utils"
// Bootstrap components.
@import "../../../pillar/node_modules/bootstrap/scss/root"
@import "../../../pillar/node_modules/bootstrap/scss/reboot"
@import "../../../pillar/node_modules/bootstrap/scss/type"
@import "../../../pillar/node_modules/bootstrap/scss/images"
@import "../../../pillar/node_modules/bootstrap/scss/code"
@import "../../../pillar/node_modules/bootstrap/scss/grid"
@import "../../../pillar/node_modules/bootstrap/scss/tables"
@import "../../../pillar/node_modules/bootstrap/scss/forms"
@import "../../../pillar/node_modules/bootstrap/scss/buttons"
@import "../../../pillar/node_modules/bootstrap/scss/transitions"
@import "../../../pillar/node_modules/bootstrap/scss/dropdown"
@import "../../../pillar/node_modules/bootstrap/scss/button-group"
@import "../../../pillar/node_modules/bootstrap/scss/input-group"
@import "../../../pillar/node_modules/bootstrap/scss/custom-forms"
@import "../../../pillar/node_modules/bootstrap/scss/nav"
@import "../../../pillar/node_modules/bootstrap/scss/navbar"
@import "../../../pillar/node_modules/bootstrap/scss/card"
@import "../../../pillar/node_modules/bootstrap/scss/breadcrumb"
@import "../../../pillar/node_modules/bootstrap/scss/pagination"
@import "../../../pillar/node_modules/bootstrap/scss/badge"
@import "../../../pillar/node_modules/bootstrap/scss/jumbotron"
@import "../../../pillar/node_modules/bootstrap/scss/alert"
@import "../../../pillar/node_modules/bootstrap/scss/progress"
@import "../../../pillar/node_modules/bootstrap/scss/media"
@import "../../../pillar/node_modules/bootstrap/scss/list-group"
@import "../../../pillar/node_modules/bootstrap/scss/close"
@import "../../../pillar/node_modules/bootstrap/scss/modal"
@import "../../../pillar/node_modules/bootstrap/scss/tooltip"
@import "../../../pillar/node_modules/bootstrap/scss/popover"
@import "../../../pillar/node_modules/bootstrap/scss/carousel"
@import "../../../pillar/node_modules/bootstrap/scss/utilities"
@import "../../../pillar/node_modules/bootstrap/scss/print"
// Pillar components.
@import "../../../pillar/src/styles/apps_base"
@import "../../../pillar/src/styles/components/base"
@import "../../../pillar/src/styles/components/jumbotron"
@import "../../../pillar/src/styles/components/alerts"
@import "../../../pillar/src/styles/components/navbar"
@import "../../../pillar/src/styles/components/dropdown"
@import "../../../pillar/src/styles/components/footer"
@import "../../../pillar/src/styles/components/shortcode"
@import "../../../pillar/src/styles/components/statusbar"
@import "../../../pillar/src/styles/components/search"
@import "../../../pillar/src/styles/components/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/_notifications"
@import "../../../pillar/src/styles/_comments"
@import "../../../pillar/src/styles/_project"
@import "../../../pillar/src/styles/_project-sharing"
@import "../../../pillar/src/styles/_project-dashboard"
@import "../../../pillar/src/styles/_error"
@import "../../../pillar/src/styles/_search"
@import "../../../pillar/src/styles/plugins/_jstree"
@import "../../../pillar/src/styles/plugins/_js_select2"
// Cloud components.
@import "_project-landing"

112
src/templates/_footer.pug Normal file
View File

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

View File

@@ -0,0 +1,55 @@
include ../../../../pillar/src/templates/mixins/components
| {% macro navigation_homepage(title) %}
+nav-secondary()
+nav-secondary-link(
href="{{ url_for('cloud.open_projects') }}")
span Films
+nav-secondary-link(
href="{{ url_for('cloud.courses') }}")
span Courses
+nav-secondary-link(
href="{{ url_for('cloud.workshops') }}")
span Workshops
+nav-secondary-link(
href="{{ url_for('projects.view', project_url='textures') }}")
span Textures
+nav-secondary-link(
href="{{ url_for('projects.view', project_url='hdri') }}")
span HDRI
+nav-secondary-link(
class="{% if title == 'services' %}active{% endif %}",
href="{{ url_for('cloud.services') }}")
span Services
| {% endmacro %}
| {% macro navigation_collection(title) %}
+nav-secondary
| {% if title in ['courses', 'workshops', 'production'] %}
+nav-secondary-link(
class="{% if title == 'courses' %}active{% endif %}",
href="{{ url_for('cloud.courses') }}")
span Courses
+nav-secondary-link(
class="{% if title == 'workshops' %}active{% endif %}",
href="{{ url_for('cloud.workshops') }}")
span Workshops
+nav-secondary-link(
class="{% if title == 'production' %}active{% endif %}",
href="{{ url_for('cloud.production') }}")
span.new Production Lessons
| {% elif title in ['open-projects'] %}
+nav-secondary-link(
class="{% if title == 'open-projects' %}active{% endif %}",
href="{{ url_for('projects.view', project_url='gallery') }}")
span Open Projects
| {% endif %}
| {% endmacro %}

View File

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

View File

@@ -1,12 +1,15 @@
| {% extends 'layout.html' %}
| {% from '_macros/_navigation.html' import navigation_tabs %}
| {% from '_macros/_navigation.html' import navigation_homepage %}
| {% from '_macros/_asset_list_item.html' import asset_list_item %}
| {% from 'nodes/custom/blog/_macros.html' import render_blog_post %}
include ../../../pillar/src/templates/mixins/components
| {% set title = 'homepage' %}
| {% block og %}
meta(property="og:type", content="website")
meta(property="og:url", content="https://cloud.blender.org/")
meta(property="og:url", content="{{ request.url }}")
meta(property="og:title", content="Blender Cloud")
meta(name="twitter:title", content="Blender Cloud")
@@ -18,238 +21,115 @@ 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_homepage(title) }}
| {% endblock navigation_tabs %}
section.blog-stream
ul.blog-stream__list
| {% block body %}
.container-fluid.dashboard-container.imgs-fluid
.row
.col-md-8.col-xl-9
section.blog
| {% if latest_posts %}
| {% for node in latest_posts %}
| {{ render_blog_post(node) }}
| {% endfor %}
| {% else %}
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') }}")
| See All Blog Posts
.d-block.text-center
a.d-inline-block.p-3.text-muted(href="{{ url_for('main.main_blog') }}")
| See All Blog Posts
a.feed(
href="{{ url_for('main.feeds_blogs') }}",
title="Blogs Feed",
data-toggle="tooltip",
data-placement="left")
i.pi-rss
a.d-inline-block.p-3.text-muted(
href="{{ url_for('main.feeds_blogs') }}",
title="Blogs Feed",
data-toggle="tooltip",
data-placement="left")
i.pi-rss
| RSS Feed
.col-md-4.col-xl-3
section.pt-3
h6.title-underline
a.text-muted(href="{{ url_for('cloud.open_projects') }}")
| Films In Production
section.dashboard-secondary
| {{ navigation_tabs(title) }}
a(href="/p/spring/")
img.rounded(
alt="Spring Open Movie Project",
src="{{ url_for('static', filename='assets/img/projects/spring_02_450x150.jpg')}}")
section.dashboard-in-production
h4 In Production
span.section-lead.
Check out these projects currently in production!
p.text-muted.pt-2.
A poetic short film about a mountain spirit and her wise little dog. #[a.text-muted(href="/p/spring/") Check it out].
a.in-production-project(href="https://cloud.blender.org/p/spring/")
img(src="{{ url_for('static', filename='assets/img/projects/spring_450x150.jpg')}}")
p.
#[strong Spring] - A poetic short film about a mountain spirit and her wise little dog.
section.py-3
h6.title-underline What's Going On
a.in-production-project(href="https://cloud.blender.org/p/hero/")
img(src="{{ url_for('static', filename='assets/img/projects/hero_450x150.jpg')}}")
p.
#[strong Hero] - A '2D' trailer-style movie focused on getting grease pencil
production ready for Blender 2.8.
section.stream
h4 Latest Assets
ul.activity-stream__list
| {% for n in activity_stream %}
li(
class="{{ n.node_type }} {{ n.properties.content_type }} {% if n.picture %}with-picture{% endif %}",
data-url="{{ n.url }}")
a.activity-stream__list-thumbnail(
class="{{ n.properties.content_type }}",
href="{{ n.url }}")
| {% if n.picture %}
img(src="{{ n.picture.thumbnail('m', api=api) }}")
| {% endif %}
.activity-stream__list-thumbnail-icon
| {% if n.node_type == 'asset' %}
| {% if n.properties.content_type == 'video' %}
i.pi-play
| {% elif n.properties.content_type == 'image' %}
i.pi-picture
| {% elif n.properties.content_type == 'file' %}
i.pi-file-archive
| {% else %}
i.pi-folder
| {% endif %}
| {% endif %}
.activity-stream__list-details
a.title(href="{{ n.url }}")
| {{ n.name }}
| {% if n.permissions.world %}
.ribbon
span free
| {% endif %}
ul.meta
| {% if not n.picture %}
li.when
a(href="{{ n.url }}", title="{{ n._created }}") {{ n._created | pretty_date_time }}
li.who {{ n.user.full_name }}
| {% endif %}
| {% if n.attached_to %}
li.where-parent
a(href="{{ n.attached_to.url }}") {{ n.attached_to.name }}
| {% endif %}
li.where-project
a.project(href="{{ url_for('projects.view', project_url=n.project.url) }}") {{ n.project.name }}
li.what
| {% if n.node_type == 'asset' %}
| {{ n.properties.content_type | undertitle }}
| {% endif %}
| {% if n.picture %}
ul.meta.extra
li.when
a(href="{{ n.url }}", title="{{ n._created }}") {{ n._created | pretty_date_time }}
li.who {{ n.user.full_name }}
| {% endif %}
| {% endfor %}
li.activity-stream__list-item.empty#activity-stream__empty
| No items to list.
section.random-asset
h4
a(href="/search") Explore the Cloud
span.section-lead Random selection of the best assets &amp; tutorials
ul.random-asset__list
| {% for n in random_featured %}
| {% if n.picture and loop.first %}
li.random-asset__list-item.project
| {% if n.project.picture_square %}
a.random-asset__list-thumbnail(
href="{{ n.project.url }}")
img.image(src="{{ n.project.picture_square.thumbnail('s', api=api) }}")
| {% if activity_stream %}
+card-deck()(class='card-deck-vertical pl-3')
| {% for child in activity_stream %}
| {% if child.node_type not in ['comment'] %}
| {{ asset_list_item(child, current_user) }}
| {% endif %}
.random-asset__list-details
a.title(href="{{ n.project.url }}") {{ n.project.name }}
| {% if n.project.summary %}
ul.meta
li.what
a(href="{{ n.project.url }}") {{ n.project.summary }}
| {% endif %}
li.random-asset__list-item.featured
| {% if n.permissions.world %}
.ribbon
span free
| {% endif %}
a.random-asset__thumbnail(
href="{{ n.url }}",
class="{{ n.properties.content_type }}")
| {% if n.picture %}
img(src="{{ n.picture.thumbnail('l', api=api) }}")
| {% if n.properties.content_type == 'video' %}
i.pi-play
| {% endif %}
| {% endif %}
a.title(href="{{ n.url }}")
| {{ n.name }}
ul.meta
li.what
a(href="{{ n.url }}")
| {% if n.properties.content_type %}{{ n.properties.content_type | undertitle }}{% else %}Folder{% endif %}
li.where
a(href="{{ n.project.url }}")
| {{ n.project.name }}
| {% endfor %}
| {% else %}
li
| {% if n.permissions.world %}
.ribbon
span free
| {% endif %}
a.random-asset__list-thumbnail(
href="{{ n.url }}",
class="{{ n.properties.content_type }}")
| {% if n.picture %}
img.image(src="{{ n.picture.thumbnail('s', api=api) }}")
| {% else %}
| {% if n.properties.content_type == 'video' %}
i.pi-film-thick
| {% elif n.properties.content_type == 'image' %}
i.pi-picture
| {% elif n.properties.content_type == 'file' %}
i.pi-file-archive
| {% else %}
i.pi-folder
| {% endif %}
| {% endif %}
.random-asset__list-details
a.title(href="{{ n.url }}") {{ n.name }}
ul.meta
li.what
a(href="{{ n.url }}")
| {% if n.properties.content_type %}{{ n.properties.content_type }}{% else %}Folder{% endif %}
li.where
a(href="{{ n.project.url }}") {{ n.project.name }}
.card
.card-body
h6.card-title
| No assets.
| {% endif %}
| {% endfor %}
section.py-3.border-bottom.mb-3
h6.title-underline
a.text-muted(href="{{ url_for('main.nodes_search_index') }}")
| Random Awesome
section.comments
| {% if random_featured %}
+card-deck()(class='card-deck-vertical pl-3')
| {% for child in random_featured %}
| {% if child.node_type not in ['comment'] %}
| {{ asset_list_item(child, current_user) }}
| {% endif %}
| {% endfor %}
| {% else %}
.card
.card-body
h6.card-title
| No random featured.
| {% endif %}
h4 Latest Comments
section.py-3
h6.title-underline Latest Comments
ul
| {% if latest_comments %}
| {% for n in latest_comments %}
li(
class="{{ n.node_type }}",
data-url="{{ n.url }}")
ul.list-unstyled.pt-2
| {% if latest_comments %}
| {% for n in latest_comments %}
li.pb-2.mb-2.border-bottom.text-truncate
a.comment-content(href="{{ n.url }}")
| {{ n.properties.content | striptags | truncate(200) }}
a.js-comment-content.text-muted(href="{{ n.url }}")
| {{ n.properties.content | striptags | truncate(200) }}
ul.meta
li.who {{ n.user.full_name }}
| {% if n.attached_to %}
li.where-parent
a(href="{{ n.attached_to.url }}") {{ n.attached_to.name }}
.d-flex.align-items-baseline
a.text-muted.text-truncate(href="{{ n.attached_to.url }}")
small.pr-2.font-weight-bold {{ n.project.name }}
small {{ n.attached_to.name }}
| {% endif %}
li.when
a(href="{{ n.url }}", title="{{ n._created }}")
| {{ n._created | pretty_date_time }}
| {% endfor %}
.d-flex.align-items-baseline
small.pr-2.font-weight-bold {{ n.user.full_name }}
| {% else %}
li.activity-stream__list-item.empty#activity-stream__empty
| No comments... yet!
a.text-muted(href="{{ n.url }}", title="{{ n._created }}")
small {{ n._created | pretty_date }}
| {% endfor %}
| {% endif %}
| {% else %}
span
| No comments... yet!
| {% endif %}
| {% endblock %}
@@ -259,16 +139,10 @@ script.
$(function () {
/* cleanup mentions in comments */
$('.comment-content').each(function(){
$('.js-comment-content').each(function(){
$(this).text($(this).text().replace(/\*|\@|\<(.*?)\>/g, ''));
});
/* Click on the whole asset/comment row to go */
$('.activity-stream__list li, .comments ul li').click(function(e){
window.location.href = $(this).data('url');
$(this).addClass('active');
});
hopToTop(); // Display jump to top button
});
| {% endblock %}

View File

@@ -1,9 +1,11 @@
include ../../../pillar/src/templates/mixins/components
doctype
html(lang="en")
head
meta(charset="utf-8")
title {% if self.page_title() %}{% block page_title %}{% endblock %} — {% endif %}Blender Cloud
meta(name="viewport", content="width=device-width, initial-scale=1.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")
@@ -19,7 +21,7 @@ html(lang="en")
| {% block og %}
meta(property="og:title", content="Blender Cloud")
meta(property="og:url", content="https://cloud.blender.org")
meta(property="og:url", content="{{ request.url }}")
meta(property="og:type", content="website")
meta(property="og:image", content="{{ url_for('static', filename='assets/img/backgrounds/background_gleb_locomotive.jpg')}}")
meta(property="og:description", content="Blender Cloud is a web based service developed by Blender Institute that allows people to access the training videos and all the data from the open projects.")
@@ -29,282 +31,109 @@ 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.
| {% 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/tutti.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 %}
script(src="{{ url_for('static_pillar', filename='assets/js/tutti.min.js', v=9112017) }}")
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 %}
| {% if not title %}{% set title="default" %}{% endif %}
body(class="{{ title }}")
.container-page
| {% with messages = get_flashed_messages(with_categories=True) %}
| {% if messages %}
| {% 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 }}")
span {{ message }}
button.close(type="button", data-dismiss="alert")
i.pi-cancel
| {% endfor %}
| {% endif %}
| {% endwith %}
| {% for (category, message) in messages %}
.alert(role="alert", class="alert-{{ category }}")
i.alert-icon(class="{{ category }}")
span {{ message }}
button.close(type="button", data-dismiss="alert")
i.pi-cancel
| {% endfor %}
nav.navbar.navbar-expand-md.fixed-top.bg-white
+nav-secondary()
button.navbar-toggler(
data-target=".sarasa",
data-toggle="collapse",
type="button")
span.sr-only Toggle Navigation
span.navbar-toggler-icon.d-flex.align-items-center
i.pi-menu
| {% endif %}
| {% endwith %}
li.nav-item.dropdown.large
a.nav-link.dropdown-toggle.px-2(
href="{{ url_for('main.homepage') }}"
data-toggle="dropdown")
i.pi-blender-cloud
i.pi-angle-down
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
a.navbar-brand(
href="{{ url_for('main.homepage') }}",
title="Blender Cloud")
span.app-logo
i.pi-blender-cloud
| {% include 'menus/_dropdown_main.html' %}
| {% block navigation_search %}
.search-input
input#cloud-search(
type="text",
placeholder="Search assets, tutorials...")
i.search-icon.pi-search
| {% endblock navigation_search %}
| {% block navigation_tabs %}
| {% endblock navigation_tabs %}
nav.collapse.navbar-collapse
ul.nav.navbar-nav.navbar-right
| {% if node and node.properties and node.properties.category %}
| {% set category = node.properties.category %}
| {% else %}
| {% set category = title %}
| {% endif %}
| {% block navigation_search %}
| {% endblock navigation_search %}
| {% block navigation_sections %}
li
a.navbar-item(
href="{{ url_for('main.main_blog') }}",
title="Blender Cloud Blog",
data-toggle="tooltip",
data-placement="bottom",
class="{% if category == 'blog' %}active{% endif %}")
span Blog
+nav-secondary()(class="ml-auto")
| {% if node and node.properties and node.properties.category %}
| {% set category = node.properties.category %}
| {% else %}
| {% set category = title %}
| {% endif %}
li(class="dropdown libraries")
a.navbar-item.dropdown-toggle(
href="",
data-toggle="dropdown",
title="Libraries")
span Libraries
i.pi-angle-down
| {% block navigation_sections %}
ul.dropdown-menu
li
a.navbar-item(
href="{{ url_for('projects.view', project_url='hdri') }}",
title="HDRI Library",
data-toggle="tooltip",
data-placement="left")
i.pi-globe
| HDRI
li
a.navbar-item(
href="{{ url_for('projects.view', project_url='textures') }}",
title="Textures Library",
data-toggle="tooltip",
data-placement="left")
i.pi-folder-texture
| Textures
li
a.navbar-item(
href="{{ url_for('projects.view', project_url='characters') }}",
title="Character Library",
data-toggle="tooltip",
data-placement="left")
i.pi-character
| Characters
+nav-secondary-link(
href="{{ url_for('main.nodes_search_index') }}",
title="Search Blender Cloud",
data-toggle="tooltip",
data-placement="bottom",
class="py-2 px-2 text-muted")
i.pi-search
| {% endblock navigation_sections %}
li(class="dropdown libraries")
a.navbar-item.dropdown-toggle(
href="",
data-toggle="dropdown",
title="Training")
span Training
i.pi-angle-down
| {% block navigation_user %}
| {% include 'menus/notifications.html' %}
| {% include 'menus/user.html' %}
| {% endblock navigation_user %}
ul.dropdown-menu
li
a.navbar-item(
href="{{ url_for('cloud.courses') }}",
title="Courses",
data-toggle="tooltip",
data-placement="left")
i.pi-graduation-cap
| Courses
li
a.navbar-item(
href="{{ url_for('cloud.workshops') }}",
title="Workshops",
data-toggle="tooltip",
data-placement="left")
i.pi-lightbulb
| Workshops
li
a.navbar-item(
href="{{ url_for('projects.view', project_url='gallery') }}",
title="Curated artwork collection",
data-toggle="tooltip",
data-placement="left")
i.pi-image
| Art Gallery
| {% if current_user.is_anonymous %}
li
a.btn.btn-sm.btn-primary.px-4.mx-1(
href="https://store.blender.org/product/membership/",
title="Sign up") Sign up
| {% endif %}
li
a.navbar-item(
href="{{ url_for('cloud.open_projects') }}",
title="Browse all the Open Projects",
data-toggle="tooltip",
data-placement="bottom",
class="{% if category in ['open-projects', 'film'] %}active{% endif %}")
span Open Projects
li
a.navbar-item(
href="{{ url_for('cloud.services') }}",
title="Blender Cloud Services",
data-toggle="tooltip",
data-placement="bottom",
class="{% if category == 'services' %}active{% endif %}")
span Services
| {% endblock navigation_sections %}
.loader-bar
| {% if current_user.is_anonymous %}
li
a.navbar-item(
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 %}
#page-overlay
| {% endblock page_overlay %}
.page-body
| {% block body %}{% endblock %}
.page-content
#search-overlay
| {% block page_overlay %}
#page-overlay
| {% endblock page_overlay %}
.page-body
| {% block body %}{% endblock %}
| {% block footer_container %}
#footer-container
| {% block 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
.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",
title="Follow us on Twitter")
i.pi-social-twitter
.col-md-2.col-xs-6
h4
a(href="{{ url_for('main.homepage') }}")
| Blender Cloud
ul.footer-links
li
a(href="{{ url_for('main.main_blog') }}",
title="Blender Cloud Blog")
| Blog
li
a(href="{{ url_for('cloud.services') }}",
title="Blender Cloud Services")
| Services
li
a(href="{{ url_for('cloud.about') }}",
title="About Blender Cloud")
| About
li
a(href="{{ url_for('cloud.terms_and_conditions') }}",
title="Terms and Conditions")
| Terms and Conditions
li
a(href="{{ url_for('cloud.privacy') }}",
title="Privacy")
| Privacy
.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 %}
| {% include '_footer.html' %}
| {% endblock footer_container %}
#notification-pop(data-url="", data-read-toggle="")
@@ -317,10 +146,19 @@ 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_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() {
@@ -332,12 +170,21 @@ html(lang="en")
{% endif %}
});
// Enable all tooltips.
if (typeof $().tooltip != 'undefined'){
$('[data-toggle="tooltip"]').tooltip({'delay' : {'show': 0, 'hide': 0}});
}
if(typeof($.fn.popover) != 'undefined'){
$('[data-toggle="popover"]').popover();
}
// Main dropdown menu logic.
$('[data-toggle="dropdown-tab"]').hover(function(){
let tab = $(this).data('tab-target');
$('[data-toggle="dropdown-tab"]').removeClass('active');
$(this).addClass('active');
$('[data-tab]').removeClass('show');
$('[data-tab="' + tab + '"]').addClass('show');
});
| {% block footer_scripts_pre %}{% endblock %}
| {% block footer_scripts %}{% endblock %}

View File

@@ -0,0 +1,177 @@
include ../../../../pillar/src/templates/mixins/components
ul.dropdown-menu.nav-main
+nav-secondary()(
class="nav-secondary-vertical float-left bg-light border-left rounded-left")
+nav-secondary-link(
href="{{ url_for('main.homepage') }}",
data-toggle='dropdown-tab',
data-tab-target='home')
i.mr-2.pi-home
span Blender Cloud
+nav-secondary-link(
href="{{ url_for('cloud.open_projects') }}",
data-toggle='dropdown-tab',
data-tab-target='films')
i.mr-2.pi-film-thick
span Open Projects
li.nav-item
.nav-link(
data-toggle='dropdown-tab',
data-tab-target='training')
i.mr-2.pi-graduation-cap
span Learn
li.nav-item
.nav-link(
data-toggle='dropdown-tab',
data-tab-target='libraries')
i.mr-2.pi-file-archive
span Libraries
+nav-secondary-link(
href="{{ url_for('cloud.services') }}",
data-toggle='dropdown-tab',
data-tab-target='services',
class="{% if title == 'services' %}active{% endif %}")
i.mr-2.pi-whoosh
span Services
.dropdown-menu-tab(data-tab='home')
.dropdown-menu-column
+nav-secondary()(class="nav-secondary-vertical rounded-right border-left overflow-hidden")
+nav-secondary-link(
href="{{ url_for('main.main_blog') }}")
i.pi-newspaper
span Blog
+nav-secondary-link(
href="{{ url_for('projects.index') }}")
i.pi-star
span My Projects
| {% if current_user.has_organizations() %}
+nav-secondary-link(
href="{{ url_for('pillar.web.organizations.index') }}")
i.pi-users
span My Organizations
| {% endif %}
+nav-secondary-link(
href="{{ url_for('projects.home_project_shared_images')}}")
i.pi-picture
span Image Sharing
+nav-secondary-link(
href="{{ url_for('projects.home_project') }}")
i.pi-blender
span Blender Sync
.dropdown-menu-tab(data-tab='films')
.dropdown-menu-column
+nav-secondary()(class="nav-secondary-vertical rounded-right border-left overflow-hidden")
+nav-secondary-link(
href="{{ url_for('cloud.open_projects') }}",
class="nav-see-more border-bottom")
span.font-weight-bold
| All Open Projects
i.pi-angle-right.pl-2
+nav-secondary-link(
href="{{ url_for('projects.view', project_url='spring') }}")
span Spring
+nav-secondary-link(
href="{{ url_for('projects.view', project_url='hero') }}")
span Hero
+nav-secondary-link(
href="{{ url_for('projects.view', project_url='dailydweebs') }}")
span The Daily Dweebs
+nav-secondary-link(
href="{{ url_for('projects.view', project_url='agent-327') }}")
span Agent 327
.dropdown-menu-tab(data-tab='training')
.dropdown-menu-column
+nav-secondary()(class="nav-secondary-vertical rounded-right border-left overflow-hidden")
li.nav-item
.nav-link.border-bottom.pointer-events-none
span.font-weight-bold
| Learn
+nav-secondary-link(
href="{{ url_for('cloud.courses') }}")
i.pi-graduation-cap
span Courses
+nav-secondary-link(
href="{{ url_for('cloud.workshops') }}")
i.pi-lightbulb
span Workshops
+nav-secondary-link(
href="{{ url_for('cloud.production') }}")
i.pi-puzzle
span.new Production Lessons
.dropdown-menu-tab(data-tab='libraries')
.dropdown-menu-column
+nav-secondary()(class="nav-secondary-vertical rounded-right border-left overflow-hidden")
li.nav-item
.nav-link.border-bottom.pointer-events-none
span.font-weight-bold
| Libraries
+nav-secondary-link(
href="{{ url_for('projects.view', project_url='textures') }}")
i.pi-folder-texture
span Textures
+nav-secondary-link(
href="{{ url_for('projects.view', project_url='hdri') }}")
i.pi-globe
span HDRI
+nav-secondary-link(
href="{{ url_for('projects.view', project_url='characters') }}")
i.pi-character
span Characters
+nav-secondary-link(
href="{{ url_for('projects.view', project_url='gallery') }}")
i.pi-picture
span Art Gallery
.dropdown-menu-tab(data-tab='services')
.dropdown-menu-column
+nav-secondary()(class="nav-secondary-vertical rounded-right border-left overflow-hidden")
+nav-secondary-link(
href="{{ url_for('cloud.services') }}",
class="nav-see-more border-bottom")
span.font-weight-bold
| All Services
i.pi-angle-right.pl-2
+nav-secondary-link(
href="/attract")
i.pi-attract
span Attract
+nav-secondary-link(
href="/flamenco")
i.pi-attract
span Flamenco
+nav-secondary-link(
href="{{ url_for('cloud.services') }}#blender-cloud-add-on")
i.pi-blender
span Blender Cloud add-on
+nav-secondary-link(
href="{{ url_for('cloud.services') }}#texture-browser")
i.pi-texture
span Texture & HDRI Browser

View File

@@ -9,8 +9,8 @@
| {% endif %}
| {% block menu_avatar %}
a.navbar-item.dropdown-toggle(href="#", data-toggle="dropdown", title="{{ current_user.email }}")
img.gravatar(
a.navbar-item.dropdown-toggle(href="{{ url_for('settings.profile') }}", data-toggle="dropdown")
img.gravatar.rounded-circle(
src="{{ current_user.gravatar }}",
class="{{ subscription }}",
alt="Avatar")
@@ -28,26 +28,26 @@ a.navbar-item.dropdown-toggle(href="#", data-toggle="dropdown", title="{{ curren
| {% block menu_list %}
li.subscription-status(class="{{ subscription }}")
| {% if subscription == 'subscriber' %}
a.navbar-item(
href="{{url_for('settings.billing')}}"
title="View subscription info")
a.navbar-item.pt-2.pl-2.pr-3(
href="{{ url_for('settings.billing') }}"
title="View subscription info")
i.pi-grin
span Your subscription is active!
span.subitem Your subscription is active!
| {% elif subscription == 'demo' %}
a.navbar-item(
href="{{url_for('settings.billing')}}"
title="View subscription info")
a.navbar-item.pt-2.pl-2.pr-3(
href="{{url_for('settings.billing')}}"
title="View subscription info")
i.pi-heart-filled
span You have a free account.
span.subitem You have a free account.
| {% elif current_user.has_cap('can-renew-subscription') %}
a.navbar-item(target='_blank', href="/renew", title="Renew subscription")
a.navbar-item.pt-2.pl-2.pr-3(target='_blank', href="/renew", title="Renew subscription")
i.pi-heart
span.info Your subscription is not active.
span.renew Click here to renew.
| {% else %}
a.navbar-item(
href="https://store.blender.org/product/membership/"
title="Renew subscription")
href="https://store.blender.org/product/membership/"
title="Renew subscription")
i.pi-unhappy
span.info Your subscription is not active.
span.renew Click here to renew.
@@ -56,9 +56,9 @@ li.subscription-status(class="{{ subscription }}")
| {{ super() }}
li
a.navbar-item(
href="{{ url_for('settings.billing') }}"
title="Billing")
a.navbar-item.px-2(
href="{{ url_for('settings.billing') }}"
title="Billing")
i.pi-credit-card
| Subscription
| {% endblock menu_list %}

View File

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

View File

@@ -0,0 +1,213 @@
| {% extends 'layout.html' %}
| {% from '_macros/_navigation.html' import navigation_tabs %}
include ../../../../pillar/src/templates/mixins/components
| {% set title = 'organizations' %}
| {% block page_title %}Organizations{% endblock %}
| {% block og %}
meta(property="og:title", content="Dashboard")
meta(name="twitter:title", content="Blender Cloud")
meta(property="og:url", content="https://cloud.blender.org/{{ request.path }}")
meta(property="og:type", content="website")
meta(property="og:image", content="{{ url_for('static', filename='assets/img/backgrounds/cloud_services_oti.jpg')}}")
meta(name="twitter:image", content="{{ url_for('static', filename='assets/img/backgrounds/cloud_services_oti.jpg')}}")
| {% endblock %}
| {% block navigation_tabs %}
| {{ navigation_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 %}

View File

@@ -0,0 +1,52 @@
| {% extends 'layout.html' %}
| {% from '_macros/_navigation.html' import navigation_collection %}
include ../../../pillar/src/templates/mixins/components
mixin group(title, tag)
.row
section.py-3.my-3.border-bottom.col-12
h4.title-underline.mb-4= title
+card-deck(data-asset-tag=tag, class="js-asset-list py-3")
| {% block page_title %}Production Lessons{% endblock %}
| {% set page_header_text = "Tips and tricks by the Open Movie crew." %}
| {% block navigation_tabs %}
| {{ navigation_collection(title) }}
| {% endblock navigation_tabs %}
| {% block head %}
script(src="{{ url_for('static_cloud', filename='assets/js/tagged_assets.min.js') }}")
script.
$(function() {
$('.js-asset-list').loadTaggedAssets(5, 3);
});
| {% endblock %}
| {% block body %}
+jumbotron(
'{{ self.page_title() }}',
'{{ page_header_text }}',
"{{ url_for('static', filename='assets/img/backgrounds/background_agent327_04.jpg')}}")(
class="jumbotron-overlay")
.container
+group('Modeling', 'modeling')
+group('Sculpting', 'sculpting')
+group('Animation', 'animation')
+group('Shading', 'shading')
+group('Texturing', 'texturing')
+group('Character Pipeline', 'character-pipeline')
+group('Rigging', 'rigging')
+group('Lighting & Rendering', 'lighting')
+group('Simulation & Effects', 'effects')
+group('Video Editing', 'video-editing')
a.d-block.py-5.text-center.text-muted(
href="{{ url_for('main.nodes_search_index') }}")
| Search Blender Cloud to find even more content
i.pi-angle-right.pl-1
| {% endblock body%}

View 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 %}

View File

@@ -0,0 +1,51 @@
| {% extends 'layout.html' %}
| {% from '_macros/_navigation.html' import navigation_tabs %}
include ../../../../pillar/src/templates/mixins/components
| {% set title = 'home' %}
| {% block og %}
meta(property="og:type", content="website")
meta(property="og:url", content="https://cloud.blender.org{{ request.path }}")
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')}}")
span Blender Sync
+nav-secondary-link(id="subtab-images", data-tab-url="{{ url_for('projects.home_project_shared_images')}}")
span Images
| {% block currenttab %}{% endblock %}
| {% endblock %}
| {% block footer_scripts %}
script.
$(document).ready(function () {
$('#subtab-{{ subtab }}').addClass('active');
var $nav_tabs = $('#sub-nav-tabs__list').find('a.nav-link');
$nav_tabs.on('click', function (e) {
console.log($(this));
window.location = $(this).attr('data-tab-url');
});
});
| {% endblock %}

View File

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

View File

@@ -0,0 +1,179 @@
| {% extends 'layout.html' %}
include ../../../../pillar/src/templates/mixins/components
| {% import 'projects/_macros.html' as projectmacros %}
| {% from '_macros/_asset_list_item.html' import asset_list_item %}
| {% block page_title %}{{ project.name }}{% endblock%}
| {% block og %}
meta(property="og:type", content="website")
| {% if og_picture %}
meta(property="og:image", content="{{ og_picture.thumbnail('l', api=api) }}")
meta(name="twitter:image", content="{{ og_picture.thumbnail('l', api=api) }}")
| {% elif node and node.picture %}
meta(property="og:image", content="{{ node.picture.thumbnail('l', api=api) }}")
meta(name="twitter:image", content="{{ node.picture.thumbnail('l', api=api) }}")
| {% elif project.picture_header %}
meta(property="og:image", content="{{ project.picture_header.thumbnail('l', api=api) }}")
meta(name="twitter:image", content="{{ project.picture_header.thumbnail('l', api=api) }}")
| {% endif %}
| {% if show_project %}
meta(property="og:title", content="{{ project.name }} - Blender Cloud")
meta(name="twitter:title", content="{{ project.name }} - Blender Cloud")
meta(property="og:description", content="{{ project.summary }}")
meta(name="twitter:description", content="{{ project.summary }}")
meta(property="og:url", content="{{ url_for('projects.view', project_url=project.url, _external=True) }}")
| {% else %}
| {% if node %}
meta(property="og:title", content="{{ node.name }} - Blender Cloud")
meta(name="twitter:title", content="{{ node.name }} on Blender Cloud")
| {% if node.node_type == 'post' %}
| {% if node.properties.content %}
meta(property="og:description", content="{{ node.properties.content | truncate(180) }}")
meta(name="twitter:description", content="{{ node.properties.content | truncate(180) }}")
| {% else %}
meta(property="og:description", content="Blender Cloud, your source for open content and training")
meta(name="twitter:description", content="Blender Cloud, your source for open content and training")
| {% endif %}
| {% else %}
| {% if node.description %}
meta(property="og:description", content="{{ node.description | truncate(180) }}")
meta(name="twitter:description", content="{{ node.description | truncate(180) }}")
| {% else %}
meta(property="og:description", content="Blender Cloud, your source for open content and training")
meta(name="twitter:description", content="Blender Cloud, your source for open content and training")
| {% endif %}
| {% endif %}
meta(property="og:url", content="{{url_for('projects.view_node', project_url=project.url, node_id=node._id, _external=True)}}")
| {% else %}
meta(property="og:title", content="{{ project.name }} Blog on Blender Cloud")
meta(name="twitter:title", content="{{ project.name }} Blog on Blender Cloud")
meta(property="og:description", content="{{ project.summary }}")
meta(name="twitter:description", content="{{ project.summary }}")
meta(property="og:url", content="{{url_for('projects.view', project_url=project.url, _external=True)}}")
| {% endif %}
| {% endif %}
| {% endblock og %}
| {% block page_overlay %}
#page-overlay.video
.video-embed
#others
| {% endblock %}
| {% block css %}
link(href="{{ url_for('static_pillar', filename='assets/css/font-pillar.css') }}", rel="stylesheet")
link(href="{{ url_for('static_cloud', filename='assets/css/project-main.css') }}", rel="stylesheet")
| {% endblock %}
| {% block navigation_tabs %}
| {{ projectmacros.render_secondary_navigation(project, navigation_links, title) }}
| {% endblock navigation_tabs %}
| {% block body %}
+jumbotron(null, null, "{{ project.picture_header.thumbnail('h', api=api) }}")
.container-fluid.landing
.row
.col-md-8.mx-auto
h2.pt-5 {{ project.name }}
| {% if project.description %}
.node-details-description
| {{ project | markdowned('description') }}
| {% endif %}
.row
.col-md-10.mx-auto
section.py-5
h2.pb-3.text-center Gallery
.gallery
| {% for n in activity_stream %}
| {% if n.node_type not in ['comment', 'post'] and n.picture %}
.thumbnail.expand-image-links
.img-container
a.js-open-overlay(href="{{ n.picture.thumbnail('l', api=api) }}", data-node_id="{{ n._id }}")
img(src="{{ n.picture.thumbnail('l', api=api) }}", alt="{{ n.name }}")
.img-caption.table
| {# Not using for the moment
span.table-cell {{ n.name }}
| #}
| {% endif %}
| {% endfor %}
div(class="clearfix")
| {% if project.nodes_featured %}
| {# In some cases featured_nodes might might be embedded #}
| {% if '_id' in project.nodes_featured[0] %}
| {% set featured_node_id=project.nodes_featured[0]._id %}
| {% else %}
| {% set featured_node_id=project.nodes_featured[0] %}
| {% endif %}
.text-center.p-5
a.btn.btn-outline-secondary.px-5(
href="{{ url_for('projects.view_node', project_url=project.url, node_id=featured_node_id) }}")
| See More Artwork
| {% endif %}
.row
.col-md-10.mx-auto
h2.pb-3.text-center Latest Updates
| {% if activity_stream %}
+card-deck(class="px-2")
| {% for n in activity_stream %}
| {% if n.node_type == 'post' %}
| {{ asset_list_item(n, current_user) }}
| {% endif %}
| {% endfor %}
| {% endif %}
.text-center.p-5
a.btn.btn-outline-secondary.px-5(href="{{ url_for('main.project_blog', project_url=project.url) }}") See All Updates
| {% 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.js-open-overlay").on( "click", function(e) {
e.preventDefault();
e.stopPropagation();
$('#page-overlay').addClass('active');
var url = $(this).attr('href');
$('#page-overlay').html('<img src="' + url + '"/>')
});
| {% endblock %}

View File

@@ -0,0 +1,645 @@
| {% extends 'layout.html' %}
| {% from '_macros/_add_new_menu.html' import add_new_menu %}
| {% from 'projects/_macros.html' import render_secondary_navigation %}
include ../../../../pillar/src/templates/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/video.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_cloud', filename='assets/css/project-main.css') }}", rel="stylesheet")
| {% endblock %}
| {% block navigation_tabs %}
| {{ render_secondary_navigation(project, navigation_links, title) }}
| {% endblock navigation_tabs %}
| {% block body %}
#project-container
#project-side-container
#project_sidebar.bg-white
ul.project-tabs.p-0
li.tabs-browse.active(
title="Browse",
data-toggle="tooltip",
data-placement="right")
a(href="{{url_for('projects.view', project_url=project.url, _external=True)}}")
i.pi-folder
| {% if not project.is_private %}
| {% if current_user_is_subscriber %}
li.tabs-search(
title="Search",
data-toggle="tooltip",
data-placement="right")
a(href="{{ url_for('projects.search', project_url=project.url, _external=True)}} ")
i.pi-search
| {% else %}
li.tabs-search(
title="Search (subscribers only)",
data-toggle="tooltip",
data-placement="right")
a(href="{{ url_for('cloud.join') }}")
i.pi-search
| {% endif %}
| {% endif %}
| {{ extension_sidebar_links }}
| {% if project.has_method('PUT') %}
li(
title="Edit Project",
data-toggle="tooltip",
data-placement="right")
a(href="{{ url_for('projects.edit', project_url=project.url) }}")
i.pi-cog
| {% endif %}
#project_nav(class="{{ title }}")
#project_nav-container
| {% if title != 'about' %}
| {% block project_tree %}
#project_tree.bg-light.px-1.py-2.border-right
| {% endblock project_tree %}
| {% endif %}
#project_context-container
| {% if project.has_method('PUT') %}
#project_context-header.position-fixed
ul.project-edit-tools.disabled.d-flex.list-unstyled.py-2.mb-0
li.dropdown(
title="Create...",
data-toggle="tooltip",
data-placement="left")
button.dropdown-toggle.btn.btn-sm.btn-outline-secondary(
id="item_add",
class="project-mode-view",
type="button",
data-toggle="dropdown",
aria-haspopup="true",
aria-expanded="false")
i.pi-collection-plus
ul.dropdown-menu.dropdown-menu-right(
class="add_new-menu")
| {{ add_new_menu(project.node_types) }}
li.button-edit
a.btn.btn-sm.btn-outline-secondary.ml-2.px-2(
id="item_edit",
class="project-mode-view",
href="javascript:void(0);",
title="Edit",
data-project_id="{{project._id}}",
data-toggle="tooltip",
data-placement="top")
i.button-edit-icon.pi-edit
li.dropdown
button.dropdown-toggle.btn.btn-sm.btn-outline-secondary.mx-2(
class="project-mode-view",
type="button",
data-toggle="dropdown",
aria-haspopup="true",
aria-expanded="false")
i.pi-more-vertical.p-0
ul.dropdown-menu.dropdown-menu-right
| {% if current_user.has_cap('admin') %}
li
a.dropdown-item(
id="item_featured",
href="javascript:void(0);",
title="Feature on project's homepage",
data-toggle="tooltip",
data-placement="left")
i.pi-star.pr-2
| Toggle Featured
li
a.dropdown-item(
id="item_toggle_public",
href="javascript:void(0);",
title="Make it accessible to anyone",
data-toggle="tooltip",
data-placement="left")
i.pi-lock-open.pr-2
| Toggle Public
| {% endif %}
li
a.dropdown-item(
id="item_toggle_projheader",
href="javascript:void(0);",
title="Feature as project's header",
data-toggle="tooltip",
data-placement="left")
i.pi-star.pr-2
| Toggle Project Header video
li.button-move
a.dropdown-item(
id="item_move",
href="javascript:void(0);",
title="Move into a folder...",
data-toggle="tooltip",
data-placement="left")
i.button-move-icon.pi-move.pr-2
| Move
li.button-delete
a.dropdown-item(
id="item_delete",
href="javascript:void(0);",
title="Can be undone within a month",
data-toggle="tooltip",
data-placement="left")
i.pi-trash.pr-2
| Delete Project
// Edit Mode
li.button-cancel
a.btn.btn-outline-secondary(
id="item_cancel",
class="project-mode-edit",
href="javascript:void(0);",
title="Cancel changes")
i.button-cancel-icon.pi-cancel
| Cancel
li.button-save
a.btn.btn-outline-success.mx-2(
id="item_save",
class="project-mode-edit",
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().
$('.loader-bar').removeClass('active');
}
| {% 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) {
$('.loader-bar').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(){
$('.loader-bar').removeClass('active');
$('.button-edit-icon').addClass('pi-edit').removeClass('pi-spin spin');
});
}
function loadProjectContent(url) {
$('.loader-bar').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(){
$('.loader-bar').removeClass('active');
$('.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-film-thick", "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 current_user_is_subscriber %}
$(projectTree).addClass('is_subscriber');
{% endif %}
| {% endblock %}
| {% block comment_scripts %} {% endblock%}

View File

@@ -1,4 +1,7 @@
| {% extends 'layout.html' %}
| {% from '_macros/_navigation.html' import navigation_collection %}
include ../../../pillar/src/templates/mixins/components
| {# Default case is Open Projects #}
| {% set page_title = 'Open Projects' %}
@@ -9,20 +12,20 @@
| {% 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 %}
| {% block og %}
meta(property="og:type", content="website")
meta(property="og:url", content="https://cloud.blender.org")
meta(property="og:url", content="{{ request.url }}")
meta(property="og:title", content="{{ page_title }} on Blender Cloud")
meta(name="twitter:title", content="{{ page_title }} on Blender Cloud")
@@ -38,59 +41,77 @@ meta(name="twitter:image", content="{{ page_header_image }}")
| {{ page_title }}
| {% endblock %}
| {% block navigation_tabs %}
| {{ navigation_collection(title) }}
| {% endblock navigation_tabs %}
| {% block body %}
#project-container
| {# Specify the URL of projects in production. These are hidden from the listing below. #}
| {% set projects_in_production = ['spring'] %}
- var jumbotron_title = 'SPRING';
- var jumbotron_lead = 'A poetic short film about a mountain spirit and her wise little dog. Check it out.';
+jumbotron(
jumbotron_title,
jumbotron_lead,
"{{ url_for('static', filename='assets/img/backgrounds/background_spring_01.jpg')}}")(
class="jumbotron-overlay")
#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 }}
a.btn.btn-primary.mt-4.px-4(
href="{{ url_for('projects.view', project_url='spring') }}") Browse the Project
a.btn.btn-link-light.mt-4(
style="color: white",
href="{{ url_for('main.project_blog', project_url='spring') }}")
| Read the Blog
i.pi-angle-right.px-1
.node_index-collection
.container.pb-5
.row
.col-12
.pt-4
h2.text-uppercase.font-weight-bold
| {{ page_title }}
.lead
| {{ page_header_text }}
| {% for project in projects %}
| {% if (project.status == 'published') or (project.status == 'pending' and current_user.is_authenticated) and project._id != config.MAIN_PROJECT_ID %}
hr.pb-2
.node_index-collection-card.project(
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) }}")
| {% endif %}
+card-deck(3)
| {% for project in projects %}
.item-info
a.item-title(
href="{{ url_for('projects.view', project_url=project.url) }}")
| {{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
| {{project.summary|safe}}
| {% if project.url not in projects_in_production %}
| {% if (project.status == 'published') or (project.status == 'pending' and current_user.is_authenticated) and project._id != config.MAIN_PROJECT_ID %}
+card(
class='js-project-go card-fade cursor-pointer mb-4 mx-0',
data-url="{{ url_for('projects.view', project_url=project.url) }}",
tabindex='{{ loop.index }}')
| {% if project.picture_header %}
a(href="{{ url_for('projects.view', project_url=project.url) }}")
img.card-img-top(
src="{{ project.picture_header.thumbnail('l', api=api) }}", alt="{{ project.name }}")
| {% endif %}
a.learn-more LEARN MORE
| {% endif %}
| {% endfor %}
.card-body
h5.card-title
| {{ project.name }}
| {% if project.status == 'pending' and current_user.is_authenticated and current_user.has_role('admin') %}
small (pending)
| {% endif %}
| {% if project.summary %}
p.card-text
| {{project.summary|safe}}
| {% endif %}
| {% endif %}
| {% 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');
});

View File

@@ -1,10 +1,13 @@
| {% extends 'layout.html' %}
| {% from '_macros/_navigation.html' import navigation_homepage %}
| {% block page_title %}Services{% endblock %}
| {% set title = 'services' %}
include ../../../pillar/src/templates/mixins/components
| {% block og %}
meta(property="og:type", content="website")
meta(property="og:url", content="{{ url_for('cloud.services') }}")
meta(property="og:url", content="{{ request.url }}")
meta(property="og:title", content="Services - Blender Cloud")
meta(name="twitter:title", content="Services - Blender Cloud")
@@ -14,210 +17,211 @@ meta(property="og:image", content="{{ url_for('static', filename='assets/img/bac
meta(name="twitter:image", content="{{ url_for('static', filename='assets/img/backgrounds/background_services.jpg')}}")
| {% endblock %}
| {% block navigation_tabs %}
| {{ navigation_homepage(title) }}
| {% endblock navigation_tabs %}
| {% block page_overlay %}
#page-overlay.video
.video-embed
| {% 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')}}")(class="jumbotron-overlay")
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!
- 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
.page-card-summary
p.
The Blender Cloud add-on provides access to most of our services directly within Blender.
p.
Use the add-on to share images online, submit renders to Flamenco or browse textures and HDRI libraries!
.navbar-backdrop-overlay
hr
- var addon_text = 'Available through the <a href="{{ url_for(\'cloud.services\') }}#blender-cloud-add-on">Blender Cloud add-on</a>'
small Blender Cloud add-on requires Blender 2.78 or newer
#page-content
a.btn.btn-primary(
href="/r/downloads/blender_cloud-latest-addon.zip")
i.pi-download
| Download add-on &nbsp;<small>v</small> {{ config.BLENDER_CLOUD_ADDON_VERSION }}
section#blender-cloud-add-on.page-card
.page-card-side
h2.page-card-title
| Blender Cloud add-on
.page-card-summary
p.
The Blender Cloud add-on provides access to most of our services directly within Blender.
p.
Use the add-on to share images online, submit renders to Flamenco or browse textures and HDRI libraries!
.page-card-side
img(
src="{{ url_for('static', filename='assets/img/features/blender_cloud_addon_thumbnail.png')}}")
hr
section#blender-sync.page-card.right
.page-card-side
h2.page-card-title Blender Sync
.page-card-summary
| Save your settings once. Use them anywhere.
| Carry your Blender configuration with you,
| use our add-on to sync your keymaps and preferences.
hr
small Blender Sync is <strong>free</strong> for everyone! No subscription required.
small This add-on requires Blender 2.78 or newer.
small Blender Cloud add-on requires Blender 2.78 or newer
.tip !{addon_text}
a.page-card-cta.download(
href="https://cloud.blender.org/r/downloads/blender_cloud-latest-addon.zip")
i.pi-download
| Download add-on &nbsp;<small>v</small> {{ config.BLENDER_CLOUD_ADDON_VERSION }}
a.btn.btn-outline-primary(
href="/r/downloads/blender_cloud-latest-addon.zip")
i.pi-download
| Download add-on &nbsp;<small>v</small> {{ config.BLENDER_CLOUD_ADDON_VERSION }}
.page-card-side
img(
src="{{ url_for('static', filename='assets/img/features/blender_cloud_addon_thumbnail.png')}}")
a.btn.btn-link(
href="/blog/introducing-blender-sync")
| Learn More
i.pi-angle-right
section#blender-sync.page-card.right
.page-card-side
h2.page-card-title Blender Sync
.page-card-summary
| Save your settings once. Use them anywhere.
| Carry your Blender configuration with you,
| use our add-on to sync your keymaps and preferences.
hr
small Blender Sync is <strong>free</strong> for everyone! No subscription required.
small This add-on requires Blender 2.78 or newer.
.tip !{addon_text}
.page-card-side
img(
src="{{ url_for('static', filename='assets/img/features/sync_thumbnail.jpg')}}")
a.page-card-cta(
href="https://cloud.blender.org/blog/introducing-blender-sync")
| Learn More
section#texture-browser.page-card.right
.page-card-side
h2.page-card-title Texture & HDRI Browser
.page-card-summary
p.
Access the <a href="/p/textures/">Blender Cloud Textures</a>
library from within Blender using our exclusive add-on.
Create, manage and share <em>your own</em> texture libraries!
.page-card-side
img(
src="{{ url_for('static', filename='assets/img/features/sync_thumbnail.jpg')}}")
.tip !{addon_text}
a.btn.btn-outline-primary.js-watch-video(
href="https://www.youtube.com/watch?v=-srXYv2Osjw",
data-youtube-id="-srXYv2Osjw")
i.pi-play
| Watch Video
.page-card-side
img(
src="{{ url_for('static', filename='assets/img/features/tex_library_thumbnail.jpg')}}")
section#texture-browser.page-card.right
.page-card-side
h2.page-card-title Texture & HDRI Browser
.page-card-summary
p.
Access the <a href="https://cloud.blender.org/p/textures/">Blender Cloud Textures</a>
library from within Blender using our exclusive add-on.
Create, manage and share <em>your own</em> texture libraries!
section#image-sharing.page-card.right
.page-card-side
h2.page-card-title Image Sharing
.page-card-summary
| Got a nice render, a Blender oddity, a cool screenshot?
| Share it instantly from within Blender to the Cloud, to the world!
.tip !{addon_text}
.tip !{addon_text}
a.page-card-cta.js-watch-video.download(
href="https://www.youtube.com/watch?v=-srXYv2Osjw",
data-youtube-id="-srXYv2Osjw")
i.pi-play
| Watch Video
a.btn.btn-outline-primary.js-watch-video(
href="https://www.youtube.com/watch?v=yvtqeMBOAyk",
data-youtube-id="yvtqeMBOAyk")
i.pi-play
| Watch Video
.page-card-side
img(
src="{{ url_for('static', filename='assets/img/features/tex_library_thumbnail.jpg')}}")
a.btn.btn-link(
href="/blog/introducing-image-sharing")
| Learn More
i.pi-angle-right
.page-card-side
img(
src="{{ url_for('static', filename='assets/img/features/image_sharing_thumbnail.jpg')}}")
section#image-sharing.page-card.right
.page-card-side
h2.page-card-title Image Sharing
.page-card-summary
| Got a nice render, a Blender oddity, a cool screenshot?
| Share it instantly from within Blender to the Cloud, to the world!
section#projects.page-card.right
.page-card-side
h2.page-card-title Private Projects
.page-card-summary.
Create and manage your own personal projects.
Upload assets and collaborate with other Blender Cloud members.
.tip !{addon_text}
a.btn.btn-link(
href="/blog/introducing-private-projects")
| Learn More
i.pi-angle-right
a.page-card-cta.download.js-watch-video(
href="https://www.youtube.com/watch?v=yvtqeMBOAyk",
data-youtube-id="yvtqeMBOAyk")
i.pi-play
| Watch Video
a.page-card-cta.outline(
href="https://cloud.blender.org/blog/introducing-image-sharing")
| Learn More
.page-card-side
img(
src="{{ url_for('static', filename='assets/img/features/image_sharing_thumbnail.jpg')}}")
.page-card-side
img(
src="{{ url_for('static', filename='assets/img/features/projects_thumbnail.jpg')}}")
section#projects.page-card.right
.page-card-side
h2.page-card-title Private Projects
.page-card-summary.
Create and manage your own personal projects.
Upload assets and collaborate with other Blender Cloud members.
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(
href="https://cloud.blender.org/blog/introducing-private-projects")
| Learn More
a.btn.btn-outline-primary.js-watch-video(
href="https://www.youtube.com/watch?v=b9x1rlyyt_o",
data-youtube-id="b9x1rlyyt_o")
i.pi-play
| Watch Video
.page-card-side
img(
src="{{ url_for('static', filename='assets/img/features/projects_thumbnail.jpg')}}")
a.btn.btn-link(
href="/blog/attract-and-flamenco-public-beta",
title="Learn more about Attract")
| Learn More
i.pi-angle-right
.page-card-side
img(
src="{{ url_for('static', filename='assets/img/features/attract_thumbnail.jpg')}}")
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.
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(
href="https://www.youtube.com/watch?v=b9x1rlyyt_o",
data-youtube-id="b9x1rlyyt_o")
i.pi-play
| Watch Video
a.btn.btn-outline-primary.js-watch-video(
href="https://www.youtube.com/watch?v=7cnFKhsM67Q",
data-youtube-id="7cnFKhsM67Q")
i.pi-play
| Watch Video
a.page-card-cta(
href="https://cloud.blender.org/blog/attract-and-flamenco-public-beta",
title="Learn more about Attract")
| Learn More
a.btn.btn-link(
href="https://flamenco.io",
title="Learn more about Flamenco")
| Learn More
i.pi-angle-right
.page-card-side
img(
src="{{ url_for('static', filename='assets/img/features/attract_thumbnail.jpg')}}")
.page-card-side
img(
src="{{ url_for('static', filename='assets/img/features/flamenco_thumbnail.jpg')}}")
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.
| {% 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
a.page-card-cta.download.js-watch-video(
href="https://www.youtube.com/watch?v=7cnFKhsM67Q",
data-youtube-id="7cnFKhsM67Q")
i.pi-play
| Watch Video
h2.page-card-title
| All of this, plus hours of training and production assets.
a.page-card-cta(
href="https://flamenco.io",
title="Learn more about Flamenco")
| Learn More
.page-card-summary.text-white
| Join us for only $9.90/month!
.page-card-side
img(
src="{{ url_for('static', filename='assets/img/features/flamenco_thumbnail.jpg')}}")
| {% if not current_user.has_role('subscriber') %}
section.page-card.subscribe(
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
| Join us for only $9.90/month!
a.page-card-cta(
href="https://store.blender.org/product/membership/")
| Subscribe Now
| {% endif %}
a.btn.btn-outline-light.px-3(href="https://store.blender.org/product/membership/")
i.pi-heart.mr-2
| Subscribe Now
| {% 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 +233,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&amp;showinfo=0;autoplay=1" frameborder="0" allowfullscreen></iframe>')
});

View File

@@ -5,7 +5,7 @@
| {% block og %}
meta(property="og:title", content="Blender Cloud Statistics")
meta(property="og:url", content="https://cloud.blender.org/stats")
meta(property="og:url", content="{{ request.url }}")
meta(property="og:image", content="{{ url_for('static', filename='assets/img/backgrounds/background_andy_hdribot_01.jpg')}}")
| {% endblock %}

View File

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

View File

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

View File

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

View File

@@ -1,4 +1,6 @@
| {% extends 'layout.html' %}
include ../../../pillar/src/templates/mixins/components
| {% block page_title %}Welcome{% endblock %}
| {% set title = 'join' %}
@@ -8,7 +10,7 @@
| {% block og %}
meta(property="og:title", content="Blender Cloud - Open Content Production Platform")
meta(property="og:url", content="https://cloud.blender.org/")
meta(property="og:url", content="{{ request.url }}")
meta(property="og:image", content="{{ url_for('static', filename='assets/img/backgrounds/background_dweebs_01.jpg')}}")
| {% endblock og %}
@@ -19,23 +21,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="#pricing")
span Pricing
+nav-secondary-link(href="#pricing")
span Pricing
| {% endblock navigation_sections %}
| {% block navigation_user %}
li.nav-item-sign-in
li.pr-1
| {% if current_user.is_anonymous %}
a.navbar-item(href="{{ url_for('users.login', next='/') }}")
| Log in and Explore
a.btn.btn-sm.btn-outline-primary.px-3(href="{{ url_for('users.login', next='/') }}")
| Log in &amp; Explore
| {% else %}
a.navbar-item(href="{{ url_for('main.homepage') }}")
a.btn.btn-sm.btn-outline-primary.px-3(href="{{ url_for('main.homepage') }}")
| Explore
| {% endif %}
| {% endblock navigation_user %}
| {% block body %}
#page-container.join
#page-header(
@@ -67,7 +67,7 @@ li.nav-item-sign-in
improve it for everyone's benefit.
.page-card-side
a.page-card-image(href="https://cloud.blender.org/p/caminandes-3/56bdacccc379cf00797160b0", target="_blank")
a.page-card-image(href="/p/caminandes-3/56bdacccc379cf00797160b0", target="_blank")
video(autoplay, loop)
source(src="{{ url_for('static', filename='assets/img/features/animation_review_01.mp4')}}")
@@ -89,7 +89,7 @@ li.nav-item-sign-in
Access high quality content, organized in
#[a(href="{{ url_for('cloud.courses') }}") classes],
#[a(href="{{ url_for('cloud.workshops') }}") workshop] and the
#[a(href="https://cloud.blender.org/p/gallery") art gallery],
#[a(href="/p/gallery") art gallery],
a curated artwork selection, where you can open a
.blend file and see how it was made.
@@ -105,7 +105,7 @@ li.nav-item-sign-in
| See #[a.learn(href="{{ url_for('cloud.courses') }}") Courses] & #[a.learn(href="{{ url_for('cloud.courses') }}") Workshops]
.page-card-side
a.page-card-image(href="https://cloud.blender.org/p/scripting-for-artists/")
a.page-card-image(href="/p/scripting-for-artists/")
img(
alt="Sybren teaches Python Scripting with Blender",
src="{{ url_for('static', filename='assets/img/backgrounds/background_sybren_01.jpg')}}")
@@ -113,25 +113,25 @@ 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="/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="/p/minecraft-animation-workshop/")
| LEARN MORE
.col-md-4
.triplet-card(data-url="https://cloud.blender.org/p/motion-graphics/")
.triplet-card(data-url="/p/motion-graphics/")
.triplet-card-thumbnail
img(
alt="HDRI",
@@ -140,11 +140,11 @@ li.nav-item-sign-in
h3 Motion Graphics
p.
A comprehensive guide to motion graphics techniques using Blender.
a.triplet-cta(href="https://cloud.blender.org/p/motion-graphics/")
a.triplet-cta(href="/p/motion-graphics/")
| LEARN MORE
.col-md-4
.triplet-card(data-url="https://cloud.blender.org/p/gallery")
.triplet-card(data-url="/p/gallery")
.triplet-card-thumbnail
img(
alt="Characters",
@@ -153,24 +153,25 @@ li.nav-item-sign-in
h3 Art Walk-throughs
p.
Follow the creative process and techniques behind stunning artwork.
a.triplet-cta(href="https://cloud.blender.org/p/gallery")
a.triplet-cta(href="/p/gallery")
| LEARN MORE
.row.training-other
.col-md-10.col-md-offset-1
p.
Other training:
#[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],
#[a(href="https://cloud.blender.org/p/character-animation/") Character Animation],
#[a(href="https://cloud.blender.org/p/humane-rigging/") Introduction] and
#[a(href="https://cloud.blender.org/p/blenrig/") Advanced Rigging],
#[a(href="https://cloud.blender.org/p/track-match-2/") VFX Workflow],
#[a(href="https://cloud.blender.org/p/creature-factory-2/") Creature] and
#[a(href="https://cloud.blender.org/p/venoms-lab-2/") Cartoon Character creation],
#[a(href="https://cloud.blender.org/p/chaos-evolution/") Advanced]
#[a(href="https://cloud.blender.org/p/blend-and-paint/") Digital Painting] and
#[a(href="/p/toon-character-workflow/") Toon Character Workflow],
#[a(href="/p/3d-printing/") Blender for 3D Printing],
#[a(href="/p/game-asset-creation/") Game Asset Creation],
#[a(href="/p/blenderella/") Character Modeling],
#[a(href="/p/character-animation/") Character Animation],
#[a(href="/p/humane-rigging/") Introduction] and
#[a(href="/p/blenrig/") Advanced Rigging],
#[a(href="/p/track-match-2/") VFX Workflow],
#[a(href="/p/creature-factory-2/") Creature] and
#[a(href="/p/venoms-lab-2/") Cartoon Character creation],
#[a(href="/p/chaos-evolution/") Advanced]
#[a(href="/p/blend-and-paint/") Digital Painting] and
#[a(href="{{ url_for('cloud.courses') }}") much more]!
@@ -211,48 +212,56 @@ 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="/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="/p/hero/")
| LEARN MORE
.col-md-4
.triplet-card(data-url="https://cloud.blender.org/p/agent-327/")
.triplet-card(data-url="/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="/p/spring/")
| LEARN MORE
.col-md-4
.triplet-card(data-url="https://cloud.blender.org/p/caminandes-3/")
.triplet-card(data-url="/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
a.triplet-cta(href="https://cloud.blender.org/p/caminandes-3/")
p.
Follow the adventures of Koro through the Patagonian pampas.
a.triplet-cta(href="/p/caminandes-3/")
| LEARN MORE
.row.training-other
.col-md-10.col-md-offset-1
p.
Other open movies:
#[a(href="https://cloud.blender.org/p/elephants-dream/") Elephants Dream],
#[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/glass-half/") Glass Half],
#[a(href="https://cloud.blender.org/p/dailydweebs/") The Daily Dweebs]
#[a(href="/p/elephants-dream/") Elephants Dream],
#[a(href="/p/big-buck-bunny/") Big Buck Bunny],
#[a(href="/p/sintel/") Sintel],
#[a(href="/p/tears-of-steel/") Tears of Steel],
#[a(href="/p/cosmos-laundromat/") Cosmos Laundromat],
#[a(href="/p/glass-half/") Glass Half],
#[a(href="/p/dailydweebs/") The Daily Dweebs],
#[a(href="/p/agent-327/") Agent 327]
and #[a(href="{{ url_for('cloud.open_projects') }}") more]
@@ -344,7 +353,7 @@ li.nav-item-sign-in
.pricing-display
span.currency-sign €
span.digit-int 9
span.digit-dec ,90 / month
span.digit-dec ,90 /&nbsp;month
.pricing-caption
p $11.50 USD
@@ -359,7 +368,7 @@ li.nav-item-sign-in
.pricing-display
span.currency-sign €
span.digit-int 109
span.digit-dec ,00 / year
span.digit-dec ,00 /&nbsp;year
.pricing-caption
p $119 USD
@@ -369,12 +378,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 /&nbsp;3&nbsp;months
.pricing-caption
p $32 USD

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 601 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

After

Width:  |  Height:  |  Size: 58 KiB