Merge branch 'develop' of https://github.com/LLK/scratch-www into issue/gh-543-teacher-banner

* 'develop' of https://github.com/LLK/scratch-www: (29 commits)
  Add explore redirects
  Allow access to 2014 and 2015 pages
  use route aliases for generalizable route patterns
  remove `/components` from `routes.json`
  Remove Make it Fly banner
  Fix issues in FAQ of /developers page.
  move shuffle to utility module
  fix error with shuffleArray function
  add shuffleArray function, shuffle most loved and most remixed
  use `image` to get studio thumbnail
  quick formatting fix for box header on search
  Fix merge issue
  Add localizations, minor style changes
  Removed rows from grid, changed explore tabs
  Removed extraneous image file
  Whitespace added
  Small fix
  Support for studios
  Refactoring, new grid component
  Style changes
  ...

# Conflicts:
#	src/views/splash/splash.jsx
#	src/views/splash/splash.scss
This commit is contained in:
Matthew Taylor 2016-06-15 09:45:05 -04:00
commit 626b3f008e
28 changed files with 852 additions and 166 deletions

View file

@ -44,9 +44,13 @@ var getStaticPaths = function (pathToStatic) {
* the express route and a static view file associated with the route
*/
var getViewPaths = function (routes) {
return routes.map(function (route) {
return route.pattern;
});
return routes.reduce(function (paths, route) {
var path = route.routeAlias || route.pattern;
if (paths.indexOf(path) === -1) {
paths.push(path);
}
return paths;
}, []);
};
/*

View file

@ -7,6 +7,7 @@ var Box = React.createClass({
type: 'Box',
propTypes: {
title: React.PropTypes.string.isRequired,
subtitle: React.PropTypes.string,
moreTitle: React.PropTypes.string,
moreHref: React.PropTypes.string,
moreProps: React.PropTypes.object
@ -20,6 +21,7 @@ var Box = React.createClass({
<div className={classes}>
<div className="box-header">
<h4>{this.props.title}</h4>
<h5>{this.props.subtitle}</h5>
<p>
<a href={this.props.moreHref} {...this.props.moreProps}>
{this.props.moreTitle}

View file

@ -13,7 +13,8 @@ $base-bg: $ui-white;
width: $cols4;
.box-header {
h4 {
h4,
h5 {
line-height: .9rem;
font-size: .9rem;
}
@ -25,7 +26,8 @@ $base-bg: $ui-white;
width: $cols6;
.box-header {
h4 {
h4,
h5 {
line-height: 1rem;
font-size: 1rem;
}
@ -37,7 +39,8 @@ $base-bg: $ui-white;
width: $cols8;
.box-header {
h4 {
h4,
h5 {
line-height: 1.1rem;
font-size: 1.1rem;
}
@ -49,7 +52,8 @@ $base-bg: $ui-white;
width: $cols12;
.box-header {
h4 {
h4,
h5 {
line-height: 1.1rem;
font-size: 1.1rem;
}
@ -72,17 +76,25 @@ $base-bg: $ui-white;
height: 20px;
overflow: hidden;
h4 {
h4,
h5 {
display: inline-block;
float: left;
}
h5 {
margin: 0;
padding-left: 5px;
text-transform: none;
letter-spacing: normal;
font-weight: normal;
}
p {
display: inline-block;
float: right;
margin: 1px 0 0 0;
padding: 0;
font-size: .85rem;
}
}

View file

@ -1,53 +0,0 @@
var classNames = require('classnames');
var FormattedMessage = require('react-intl').FormattedMessage;
var React = require('react');
var TitleBanner = require('../title-banner/title-banner.jsx');
var Button = require('../forms/button.jsx');
var FlexRow = require('../flex-row/flex-row.jsx');
require('./cn-banner.scss');
/**
* Homepage banner for Cartoon Network collaboration
*/
var CNBanner = React.createClass({
type: 'CNBanner',
render: function () {
var classes = classNames(
'cn-banner',
this.props.className
);
return (
<TitleBanner className={classes}>
<FlexRow className="inner">
<div className="cta">
<h1>
<FormattedMessage id='cnbanner.makeItFly' />
</h1>
<p>
<FormattedMessage id='cnbanner.flyDescription' />
</p>
<div className="button-row">
<a href="/studios/2050750/">
<Button>
<FormattedMessage id='cnbanner.seeExamples' />
</Button>
</a>
<a href="/fly/">
<Button>
<FormattedMessage id='cnbanner.makeYourOwn' />
</Button>
</a>
</div>
</div>
<div className="flying">
<img src="/svgs/make-it-fly-graphic.svg" alt="" />
</div>
</FlexRow>
</TitleBanner>
);
}
});
module.exports = CNBanner;

View file

@ -1,69 +0,0 @@
@import "../../colors";
@import "../../frameless";
.cn-banner {
background-color: $ui-blue;
background-image: url("/images/ppg-bg.jpg");
&.title-banner {
transition: none;
margin-bottom: 20px;
padding: 0;
text-align: left;
h1,
p {
margin: 0;
padding: 0;
width: 100%;
text-align: left;
}
}
h1 {
font-weight: 500;
}
p {
font-weight: 300;
}
.cta,
.flying {
display: flex;
height: 235px;
justify-content: center;
}
.cta {
margin-right: $cols1;
width: $cols5;
flex-direction: column;
}
.flying {
margin: 0 $cols1;
width: $cols4;
flex-direction: row;
}
.button-row {
display: flex;
margin-top: $gutter;
width: $cols5;
align-content: stretch;
a {
margin-right: $gutter;
flex-grow: 1;
}
.button {
background-color: $ui-white;
padding: 13px 20px;
width: 100%;
color: $ui-blue;
font-size: .9rem;
}
}
}

View file

@ -0,0 +1,130 @@
[
{
"id": 1,
"type": "project",
"title": "Project",
"thumbnailUrl": "",
"creator": "",
"href": "#"
},
{
"id": 2,
"type": "project",
"title": "Project",
"thumbnailUrl": "",
"creator": "",
"href": "#"
},
{
"id": 3,
"type": "project",
"title": "Project",
"thumbnailUrl": "",
"creator": "",
"href": "#"
},
{
"id": 4,
"type": "project",
"title": "Project",
"thumbnailUrl": "",
"creator": "",
"href": "#"
},
{
"id": 5,
"type": "project",
"title": "Project",
"thumbnailUrl": "",
"creator": "",
"href": "#"
},
{
"id": 6,
"type": "project",
"title": "Project",
"thumbnailUrl": "",
"creator": "",
"href": "#"
},
{
"id": 7,
"type": "project",
"title": "Project",
"thumbnailUrl": "",
"creator": "",
"href": "#"
},
{
"id": 8,
"type": "project",
"title": "Project",
"thumbnailUrl": "",
"creator": "",
"href": "#"
},
{
"id": 9,
"type": "project",
"title": "Project",
"thumbnailUrl": "",
"creator": "",
"href": "#"
},
{
"id": 10,
"type": "project",
"title": "Project",
"thumbnailUrl": "",
"creator": "",
"href": "#"
},
{
"id": 11,
"type": "project",
"title": "Project",
"thumbnailUrl": "",
"creator": "",
"href": "#"
},
{
"id": 12,
"type": "project",
"title": "Project",
"thumbnailUrl": "",
"creator": "",
"href": "#"
},
{
"id": 13,
"type": "project",
"title": "Project",
"thumbnailUrl": "",
"creator": "",
"href": "#"
},
{
"id": 14,
"type": "project",
"title": "Project",
"thumbnailUrl": "",
"creator": "",
"href": "#"
},
{
"id": 15,
"type": "project",
"title": "Project",
"thumbnailUrl": "",
"creator": "",
"href": "#"
},
{
"id": 16,
"type": "project",
"title": "Project",
"thumbnailUrl": "",
"creator": "",
"href": "#"
}
]

View file

@ -0,0 +1,67 @@
var classNames = require('classnames');
var React = require('react');
var Thumbnail = require('../thumbnail/thumbnail.jsx');
var FlexRow = require('../flex-row/flex-row.jsx');
require('./grid.scss');
var Grid = React.createClass({
type: 'Grid',
getDefaultProps: function () {
return {
items: require('./grid.json'),
itemType: 'projects',
showLoves: false,
showFavorites: false,
showRemixes: false,
showViews: false
};
},
render: function () {
var classes = classNames(
'grid',
this.props.className
);
return (
<div className={classes}>
<FlexRow>
{this.props.items.map(function (item) {
var href = '/' + this.props.itemType + '/' + item.id + '/';
if (this.props.itemType == 'projects') {
return (
<Thumbnail key={item.id}
showLoves={this.props.showLoves}
showFavorites={this.props.showFavorites}
showRemixes={this.props.showRemixes}
showViews={this.props.showViews}
type={'project'}
href={href}
title={item.title}
src={item.image}
creator={item.creator}
loves={item.stats.loves}
favorites={item.stats.favorites}
remixes={item.stats.remixes}
views={item.stats.views} />
);
}
else {
return (
<Thumbnail key={item.id}
type={'gallery'}
href={href}
title={item.title}
src={item.image}
owner={item.owner} />
);
}
}.bind(this))}
</FlexRow>
</div>
);
}
});
module.exports = Grid;

View file

@ -0,0 +1,43 @@
@import "../../frameless";
.grid {
display: inline-block;
padding: 12px;
justify-content: space-around;
align-items: center;
.thumbnail {
padding: 12px;
&.project {
$project-width: 200px;
$project-height: 150px;
width: $project-width;
img {
width: $project-width;
height: $project-height;
}
}
&.gallery {
$gallery-width: 200px;
$gallery-height: 118px;
width: $gallery-width;
img {
width: $gallery-width;
height: $gallery-height;
}
}
}
&.column {
flex-direction: column;
justify-content: center;
}
@media only screen and (max-width: $tablet - 1) {
flex-direction: column;
}
}

View file

@ -194,7 +194,7 @@ var Navigation = React.createClass({
</a>
</li>
<li className="link explore">
<a href="/explore?date=this_month">
<a href="/explore/projects/all">
<FormattedMessage id="general.explore" />
</a>
</li>
@ -215,14 +215,12 @@ var Navigation = React.createClass({
</li>
<li className="search">
<form action="/search/google_results" method="get">
<form action="/search/projects" method="get">
<Button type="submit" className="btn-search" />
<Input type="text"
aria-label={formatMessage({id: 'general.search'})}
placeholder={formatMessage({id: 'general.search'})}
name="q" />
<Input type="hidden" name="date" value="anytime" />
<Input type="hidden" name="sort_by" value="datetime_shared" />
</form>
</li>
{this.props.session.status === sessionActions.Status.FETCHED ? (

View file

@ -0,0 +1,26 @@
var classNames = require('classnames');
var SubNavigation = require('../../components/subnavigation/subnavigation.jsx');
var React = require('react');
require('./tabs.scss');
/**
* Container for a custom, horizontal list of navigation elements
* that can be displayed within a view or component.
*/
var Tabs = React.createClass({
type: 'Tabs',
render: function () {
var classes = classNames(
'tabs',
this.props.className
);
return (
<SubNavigation className={classes}>
{this.props.children}
</SubNavigation>
);
}
});
module.exports = Tabs;

View file

@ -0,0 +1,39 @@
@import "../../colors";
.tabs {
background-color: $ui-gray;
padding: 0 0 0 20px;
width: calc(100% - 20px);
justify-content: flex-start;
}
.tabs li {
opacity: .75;
margin-bottom: -4px;
border-top-left-radius: 10px;
border-top-right-radius: 10px;
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
background-color: $ui-white;
color: $header-gray;
&:hover {
opacity: 1;
border-color: $active-gray;
background-color: $ui-white;
color: $header-gray;
}
}
.tabs li.active {
opacity: 1;
border-bottom: 4px solid $ui-white;
&:hover {
opacity: 1;
border-bottom: 4px solid $ui-white;
background-color: $ui-white;
color: $header-gray;
}
}

View file

@ -15,7 +15,9 @@ var Thumbnail = React.createClass({
src: '',
type: 'project',
showLoves: false,
showFavorites: false,
showRemixes: false,
showViews: false,
linkTitle: true,
alt: ''
};
@ -40,23 +42,40 @@ var Thumbnail = React.createClass({
key="loves"
className="thumbnail-loves"
title={this.props.loves + ' loves'}>
{this.props.loves}
</div>
);
}
if (this.props.favorites && this.props.showFavorites) {
extra.push(
<div
key="favorites"
className="thumbnail-favorites"
title={this.favorites + ' favorites'}>
{this.props.favorites}
</div>
);
}
if (this.props.remixes && this.props.showRemixes) {
extra.push(
<div
key="remixes"
className="thumbnail-remixes"
title={this.props.remixes + ' remixes'}>
{this.props.remixes}
</div>
);
}
if (this.props.views && this.props.showViews) {
extra.push(
<div
key="views"
className="thumbnail-views"
title={this.props.views + ' views'}>
{this.props.views}
</div>
);
}
var imgElement,titleElement;
if (this.props.linkTitle) {
imgElement = <a className="thumbnail-image" href={this.props.href}>

View file

@ -10,7 +10,7 @@
}
}
$extras: ".thumbnail-creator, .thumbnail-loves, .thumbnail-remixes";
$extras: ".thumbnail-creator, .thumbnail-loves, .thumbnail-favorites,.thumbnail-remixes,.thumbnail-views";
.thumbnail-title,
#{$extras} {
@ -41,7 +41,13 @@
}
.thumbnail-loves,
.thumbnail-remixes {
.thumbnail-favorites,
.thumbnail-remixes,
.thumbnail-views {
display: inline;
margin-right: 10px;
&:before {
display: inline-block;
margin-right: .1rem;
@ -61,12 +67,24 @@
}
}
.thumbnail-favorites {
&:before {
background-image: url("/svgs/favorite/favorite_type-gray.svg");
}
}
.thumbnail-remixes {
&:before {
background-image: url("/svgs/remix/remix_type-gray.svg");
}
}
.thumbnail-views {
&:before {
background-image: url("/svgs/view/view_type-gray.svg");
}
}
&.project {
$project-width: 144px;
$project-height: 108px;

View file

@ -20,6 +20,7 @@
"general.jobs": "Jobs",
"general.joinScratch": "Join Scratch",
"general.legal": "Legal",
"general.loadMore": "Load More",
"general.learnMore": "Learn More",
"general.messages": "Messages",
"general.myClass": "My Class",
@ -28,6 +29,7 @@
"general.offlineEditor": "Offline Editor",
"general.press": "Press",
"general.privacyPolicy": "Privacy Policy",
"general.projects": "Projects",
"general.profile": "Profile",
"general.scratchConference": "Scratch Conference",
"general.scratchday": "Scratch Day",
@ -37,6 +39,7 @@
"general.search": "Search",
"general.signIn": "Sign in",
"general.statistics": "Statistics",
"general.studios": "Studios",
"general.support": "Support",
"general.tipsWindow": "Tips Window",
"general.tipsAnimateYourNameTitle": "Animate Your Name",
@ -51,6 +54,14 @@
"general.whatsHappening": "What's Happening?",
"general.wiki": "Scratch Wiki",
"general.all": "All",
"general.animations": "Animations",
"general.art": "Art",
"general.games": "Games",
"general.music": "Music",
"general.stories": "Stories",
"general.results": "Results",
"footer.discuss": "Discussion Forums",
"footer.help": "Help Page",
"footer.scratchFamily": "Scratch Family",
@ -58,11 +69,6 @@
"login.forgotPassword": "Forgot Password?",
"navigation.signOut": "Sign out",
"cnbanner.makeItFly": "Make It Fly",
"cnbanner.seeExamples": "See examples",
"cnbanner.makeYourOwn": "Create your own",
"cnbanner.flyDescription": "With Scratch, you can program anything to fly. Animate the Scratch Cat, The Powerpuff Girls, or even a taco!",
"parents.FaqAgeRangeA": "While Scratch is primarily designed for 8 to 16 year olds, it is also used by people of all ages, including younger children with their parents.",
"parents.FaqAgeRangeQ": "What is the age range for Scratch?",

21
src/lib/shuffle.js Normal file
View file

@ -0,0 +1,21 @@
/*
* Function that shuffles an array using a Fisher-Yates shuffle.
*/
module.exports.shuffle = function (arr) {
var i, j = 0;
var temp = null;
if (arr) {
var tempArray = arr.slice(0);
} else {
return arr;
}
for (i = arr.length - 1; i > 0; i -= 1) {
j = Math.floor(Math.random() * (i + 1));
temp = tempArray[i];
tempArray[i] = tempArray[j];
tempArray[j] = temp;
}
return tempArray;
};

View file

@ -11,12 +11,6 @@
"view": "about/about",
"title": "About"
},
{
"name": "components",
"pattern": "^/components/?$",
"view": "components/components",
"title": "Components"
},
{
"name": "developers",
"pattern": "^/developers/?$",
@ -29,6 +23,38 @@
"view": "hoc/hoc",
"title": "Hour of Code"
},
{
"name": "explore",
"pattern": "^/explore/:projects/:all/?$",
"routeAlias": "^/explore",
"view": "explore/explore",
"title": "Explore"
},
{
"name": "explore-redirect",
"pattern": "^/explore/?$",
"routeAlias": "^/explore",
"redirect": "/explore/projects/all"
},
{
"name": "explore-projects-redirect",
"pattern": "^/explore/projects/?$",
"routeAlias": "^/explore",
"redirect": "/explore/projects/all"
},
{
"name": "explore-studios-redirect",
"pattern": "^/explore/studios/?$",
"routeAlias": "^/explore",
"redirect": "/explore/studios/all"
},
{
"name": "search",
"pattern": "^/search/:projects?$/?$",
"routeAlias": "^/search",
"view": "search/search",
"title": "Search"
},
{
"name": "credits",
"pattern": "^/info/credits/?$",
@ -68,6 +94,7 @@
{
"name": "conference-index",
"pattern": "^/conference/?$",
"routeAlias": "^/conference(?!/201[4-5])",
"view": "conference/index/index",
"title": "Scratch Conference",
"viewportWidth": "device-width"
@ -75,6 +102,7 @@
{
"name": "conference-plan",
"pattern": "^/conference/plan/?$",
"routeAlias": "^/conference(?!/201[4-5])",
"view": "conference/plan/plan",
"title": "Plan Your Visit",
"viewportWidth": "device-width"
@ -82,6 +110,7 @@
{
"name": "conference-expectations",
"pattern": "^/conference/expect/?$",
"routeAlias": "^/conference(?!/201[4-5])",
"view": "conference/expect/expect",
"title": "What to Expect",
"viewportWidth": "device-width"
@ -89,6 +118,7 @@
{
"name": "conference-schedule",
"pattern": "^/conference/schedule/?$",
"routeAlias": "^/conference(?!/201[4-5])",
"view": "conference/schedule/schedule",
"title": "Conference Schedule",
"viewportWidth": "device-width"
@ -96,6 +126,7 @@
{
"name": "conference-details",
"pattern": "^/conference/:id/details/?$",
"routeAlias": "^/conference(?!/201[4-5])",
"view": "conference/details/details",
"title": "Event Details",
"viewportWidth": "device-width"

View file

@ -6,7 +6,6 @@ var Page = require('../../components/page/www/page.jsx');
var Box = require('../../components/box/box.jsx');
var Button = require('../../components/forms/button.jsx');
var Carousel = require('../../components/carousel/carousel.jsx');
var CNBanner = require('../../components/cn-banner/cn-banner.jsx');
var Input = require('../../components/forms/input.jsx');
var Spinner = require('../../components/spinner/spinner.jsx');
@ -66,7 +65,6 @@ var Components = React.createClass({
<span className="splash-blue">$splash-blue</span>
</div>
</div>
<CNBanner />
</div>);
}
});

View file

@ -52,7 +52,7 @@ var Developers = React.createClass({
</SubNavigation>
</div>
</TitleBanner>
<div className="inner">
<section id="projects">
<span className="nav-spacer"></span>
@ -228,11 +228,10 @@ var Developers = React.createClass({
<div className="faq column">
<h4>Are there rules to using this code in my application?</h4>
<p>
You may use this code in accordance with the{' '}
<a href="http://www.apache.org/licenses/LICENSE-2.0">Apache 2.0</a> license
which governs this project. We also strongly encourage you to consider{' '}
the learning and design principles (above, on this page) when building{' '}
creative learning experiences for kids of all ages.
You may use this code in accordance with the license which governs{' '}
each project. We also strongly encourage you to consider the learning{' '}
and design principles (above, on this page) when building creative{' '}
learning experiences for kids of all ages.
</p>
</div>
<div className="faq column">
@ -243,7 +242,7 @@ var Developers = React.createClass({
<p>
If you wish, you can publicly state that your application is powered by{' '}
Scratch Blocks. If you do so, we would also encourage you to link back to{' '}
code repository.
the code repository.
</p>
</div>
<div className="faq column">

View file

@ -0,0 +1,154 @@
var injectIntl = require('react-intl').injectIntl;
var FormattedMessage = require('react-intl').FormattedMessage;
var React = require('react');
var render = require('../../lib/render.jsx');
var Api = require('../../mixins/api.jsx');
var Page = require('../../components/page/www/page.jsx');
var Box = require('../../components/box/box.jsx');
var Tabs = require('../../components/tabs/tabs.jsx');
var SubNavigation = require('../../components/subnavigation/subnavigation.jsx');
var Grid = require('../../components/grid/grid.jsx');
require('./explore.scss');
// @todo migrate to React-Router once available
var Explore = injectIntl(React.createClass({
type: 'Explore',
mixins: [
Api
],
getDefaultProps: function () {
var categoryOptions = ['all','animations','art','games','music','stories'];
var typeOptions = ['projects','studios'];
var pathname = window.location.pathname.toLowerCase();
if (pathname[pathname.length - 1] === '/') {
pathname = pathname.substring(0, pathname.length - 1);
}
var slash = pathname.lastIndexOf('/');
var currentCategory = pathname.substring(slash + 1,pathname.length);
var typeStart = pathname.indexOf('explore/');
var type = pathname.substring(typeStart + 8,slash);
if (categoryOptions.indexOf(currentCategory) === -1 || typeOptions.indexOf(type) === -1) {
window.location = window.location.origin + '/explore/projects/all/';
}
return {
category: currentCategory,
acceptableTabs: categoryOptions,
acceptableTypes: typeOptions,
itemType: type,
loadNumber: 16
};
},
getInitialState: function () {
return {
loaded: [],
offset: 0
};
},
componentDidMount: function () {
this.getExploreMore();
},
getExploreMore: function () {
var qText = '';
if (this.props.tab != 'all') {
qText = '&q=' + this.props.category;
}
this.api({
uri: '/search/' + this.props.itemType +
'?limit=' + this.props.loadNumber +
'&offset=' + this.state.offset +
qText
}, function (err, body) {
if (!err) {
var loadedSoFar = this.state.loaded;
Array.prototype.push.apply(loadedSoFar,body);
this.setState({loaded: loadedSoFar});
var currentOffset = this.state.offset + this.props.loadNumber;
this.setState({offset: currentOffset});
}
}.bind(this));
},
changeItemType: function () {
var newType;
for (var t in this.props.acceptableTypes) {
if (this.props.itemType !== t) {
newType = t;
break;
}
}
window.location = window.location.origin + '/explore/' + newType + '/' + this.props.tab;
},
getBubble: function (type) {
var allBubble = <a href={'/explore/' + this.props.itemType + '/' + type + '/'}>
<li>
<FormattedMessage id={'general.' + type} />
</li>
</a>;
if (this.props.category === type) {
allBubble = <a href={'/explore/' + this.props.itemType+'/' + type + '/'}>
<li className='active'>
<FormattedMessage id={'general.' + type} />
</li>
</a>;
}
return allBubble;
},
getTab: function (type) {
var allTab = <a href={'/explore/' + type + '/' + this.props.category + '/'}>
<li>
<FormattedMessage id={'general.' + type} />
</li>
</a>;
if (this.props.itemType === type) {
allTab = <a href={'/explore/' + type +' /' + this.props.category + '/'}>
<li className='active'>
<FormattedMessage id={'general.' + type} />
</li>
</a>;
}
return allTab;
},
render: function () {
return (
<div>
<div className='outer'>
<Box title={'Explore'}>
<SubNavigation className='categories'>
{this.getBubble('all')}
{this.getBubble('animations')}
{this.getBubble('art')}
{this.getBubble('games')}
{this.getBubble('music')}
{this.getBubble('stories')}
</SubNavigation>
<Tabs>
{this.getTab('projects')}
{this.getTab('studios')}
</Tabs>
<div id='projectBox' key='projectBox'>
<Grid items={this.state.loaded}
itemType={this.props.itemType}
showLoves={true}
showFavorites={true}
showViews={true} />
<SubNavigation className='load'>
<button onClick={this.getExploreMore}>
<li>
<FormattedMessage id='general.loadMore' />
</li>
</button>
</SubNavigation>
</div>
</Box>
</div>
</div>
);
}
}));
render(<Page><Explore /></Page>, document.getElementById('app'));

View file

@ -0,0 +1,65 @@
@import "../../colors";
@import "../../frameless";
$base-bg: $ui-white;
#view {
.box {
display: block;
margin-right: auto;
margin-bottom: 20px;
margin-left: auto;
.box-content {
padding: 0;
}
}
.categories {
display: inline-block;
background-color: $ui-gray;
padding-left: 10px;
width: calc(100% - 10px);
justify-content: left;
li {
opacity: .75;
background-color: $ui-white;
color: $header-gray;
&:hover {
opacity: 1;
border-color: $active-dark-gray;
}
}
li.active {
opacity: 1;
border-color: $active-dark-gray;
&:hover {
opacity: 1;
border-color: $active-dark-gray;
}
}
}
#projectBox {
border-top: 2px solid;
border-color: $active-gray;
background-color: $ui-white;
padding-bottom: 30px;
width: 100%;
}
.load button {
outline: None;
border: None;
background-color: $ui-white;
padding: 0;
li {
color: $header-gray;
}
}
}

126
src/views/search/search.jsx Normal file
View file

@ -0,0 +1,126 @@
var injectIntl = require('react-intl').injectIntl;
var FormattedMessage = require('react-intl').FormattedMessage;
var React = require('react');
var render = require('../../lib/render.jsx');
var Api = require('../../mixins/api.jsx');
var Page = require('../../components/page/www/page.jsx');
var Box = require('../../components/box/box.jsx');
var SubNavigation = require('../../components/subnavigation/subnavigation.jsx');
var Tabs = require('../../components/tabs/tabs.jsx');
var Grid = require('../../components/grid/grid.jsx');
require('./search.scss');
// @todo migrate to React-Router once available
var Search = injectIntl(React.createClass({
type: 'Search',
mixins: [
Api
],
getDefaultProps: function () {
var query = window.location.search;
var pathname = window.location.pathname.toLowerCase();
if (pathname[pathname.length - 1] === '/') {
pathname = pathname.substring(0, pathname.length - 1);
}
var start = pathname.lastIndexOf('/');
var type = pathname.substring(start + 1, pathname.length);
var q = query.lastIndexOf('q=');
var term = '';
if (q !== -1) {
term = query.substring(q + 2, query.length).toLowerCase();
}
while (term.indexOf('/') > -1) {
term = term.substring(0, term.indexOf('/'));
}
while (term.indexOf('&') > -1) {
term = term.substring(0, term.indexOf('&'));
}
term = term.split('+').join(' ');
return {
tab: type,
searchTerm: term,
loadNumber: 16
};
},
getInitialState: function () {
return {
loaded: [],
offset: 0
};
},
componentDidMount: function () {
this.getSearchMore();
},
getSearchMore: function () {
var termText = '';
if (this.props.searchTerm !== '') {
termText = '&q=' + this.props.searchTerm;
}
this.api({
uri: '/search/' + this.props.tab +
'?limit=' + this.props.loadNumber +
'&offset=' + this.state.offset +
termText
}, function (err, body) {
var loadedSoFar = this.state.loaded;
Array.prototype.push.apply(loadedSoFar, body);
this.setState({loaded: loadedSoFar});
var currentOffset = this.state.offset + this.props.loadNumber;
this.setState({offset: currentOffset});
}.bind(this));
},
getTab: function (type) {
var term = this.props.searchTerm.split(' ').join('+');
var allTab = <a href={'/search/' + type + '?q=' + term + '/'}>
<li>
<FormattedMessage id={'general.' + type} />
</li>
</a>;
if (this.props.tab == type) {
allTab = <a href={'/search/' + type + '?q=' + term + '/'}>
<li className='active'>
<FormattedMessage id={'general.' + type} />
</li>
</a>;
}
return allTab;
},
render: function () {
var formatMessage = this.props.intl.formatMessage;
return (
<div>
<div className='outer'>
<Box title={formatMessage({id: 'general.results'}) + ':'}
subtitle={this.props.searchTerm}
moreProps={{className: 'subnavigation'}}>
<Tabs>
{this.getTab('projects')}
{this.getTab('studios')}
</Tabs>
<div id='projectBox' key='projectBox'>
<Grid items={this.state.loaded}
itemType={this.props.tab}
showLoves={true}
showFavorites={true}
showViews={true} />
<SubNavigation className='load'>
<button onClick={this.getSearchMore}>
<li>
<FormattedMessage id='general.loadMore' />
</li>
</button>
</SubNavigation>
</div>
</Box>
</div>
</div>
);
}
}));
render(<Page><Search /></Page>, document.getElementById('app'));

View file

@ -0,0 +1,36 @@
@import "../../colors";
@import "../../frameless";
$base-bg: $ui-white;
#view {
.box {
display: block;
margin-right: auto;
margin-bottom: 20px;
margin-left: auto;
.box-content {
padding: 0;
}
}
#projectBox {
border-top: 2px solid;
border-color: $active-gray;
background-color: $ui-white;
padding-bottom: 30px;
width: 100%;
}
.load button {
outline: None;
border: None;
background-color: $ui-white;
padding: 0;
li {
color: $header-gray;
}
}
}

View file

@ -6,12 +6,12 @@ var render = require('../../lib/render.jsx');
var permissionsActions = require('../../redux/permissions.js');
var sessionActions = require('../../redux/session.js');
var shuffle = require('../../lib/shuffle.js').shuffle;
var Api = require('../../mixins/api.jsx');
var Activity = require('../../components/activity/activity.jsx');
var AdminPanel = require('../../components/adminpanel/adminpanel.jsx');
var CNBanner = require('../../components/cn-banner/cn-banner.jsx');
var DropdownBanner = require('../../components/dropdown-banner/banner.jsx');
var Box = require('../../components/box/box.jsx');
var Button = require('../../components/forms/button.jsx');
@ -315,7 +315,8 @@ var Splash = injectIntl(React.createClass({
defaultMessage: 'What the Community is Remixing' })}
key="community_most_remixed_projects">
<Carousel items={this.state.featuredGlobal.community_most_remixed_projects} showRemixes={true} />
<Carousel items={shuffle(this.state.featuredGlobal.community_most_remixed_projects)}
showRemixes={true} />
</Box>,
<Box title={
formatMessage({
@ -323,7 +324,8 @@ var Splash = injectIntl(React.createClass({
defaultMessage: 'What the Community is Loving' })}
key="community_most_loved_projects">
<Carousel items={this.state.featuredGlobal.community_most_loved_projects} showLoves={true} />
<Carousel items={shuffle(this.state.featuredGlobal.community_most_loved_projects)}
showLoves={true} />
</Box>
);
@ -386,7 +388,6 @@ var Splash = injectIntl(React.createClass({
{this.props.permissions.educator ? [
<TeacherBanner messages={messages} />
] : []}
<CNBanner />
<div key="inner" className="inner">
{this.props.session.status === sessionActions.Status.FETCHED ? (
this.props.session.session.user ? [

View file

@ -27,7 +27,6 @@
}
}
.cn-banner,
.teacher-banner {
margin-top: -20px;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

View file

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="22" height="22" viewBox="0 0 22 22">
<path d="M16.265 18.413l-5.33-2.82-5.344 2.793 1.034-5.94-4.307-4.22 5.97-.852 2.682-5.4 2.654 5.413 5.965.882-4.328 4.198z" stroke="#6b6b6b" stroke-linejoin="round" stroke-width="1.6" fill="none"/>
</svg>

After

Width:  |  Height:  |  Size: 291 B

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 42 KiB

View file

@ -0,0 +1,12 @@
<svg xmlns="http://www.w3.org/2000/svg" width="22" height="22" viewBox="0 0 22 22">
<ellipse ry="5.47" rx="8.403" cy="10.566" cx="11.132" fill="#6b6b6b" stroke="#6b6b6b" stroke-width="1.74" stroke-linejoin="bevel"/>
<ellipse cx="11.085" cy="10.605" rx="3.67" ry="3.598" fill="none" stroke="#fff" stroke-width="2.121" stroke-linejoin="round"/>
<g stroke="#fff" fill="none" stroke-linejoin="bevel">
<path transform="matrix(1.03203 0 0 -1.1522 -.523 -1.591)" d="M20.575-18.124a12.19 9.241 0 0 1-3.78 12.088A12.19 9.241 0 0 1 .558-7.724" stroke-width="1.417"/>
<path d="M20.166 3.088a12.138 9.243 0 0 1-3.765 12.09A12.138 9.243 0 0 1 .234 13.489" stroke-width="1.414" transform="matrix(1.03203 0 0 1.1522 -.523 -1.591)"/>
</g>
<g stroke="#fff" fill="none" stroke-linejoin="bevel">
<path transform="matrix(-1.03217 0 0 -1.14523 22.758 -1.517)" d="M20.575-18.124a12.19 9.241 0 0 1-3.78 12.088A12.19 9.241 0 0 1 .558-7.724" stroke-width="1.417"/>
<path d="M20.166 3.088a12.138 9.243 0 0 1-3.765 12.09A12.138 9.243 0 0 1 .234 13.489" stroke-width="1.414" transform="matrix(-1.03217 0 0 1.14523 22.758 -1.517)"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB