Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
120 changes: 63 additions & 57 deletions src/components/navigation/www/navigation.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,20 +28,37 @@ class Navigation extends React.Component {
bindAll(this, [
'getProfileUrl',
'handleSearchSubmit',
'pollForMessages'
'pollForMessages',
'handleSearchFocus',
'handleSearchBlur'
]);
// Keep the timeout id so we can cancel it (e.g. when we unmount)
this.messageCountTimeoutId = -1;

this.state = {
searchFocused: false
};
}

handleSearchFocus () {
this.setState({searchFocused: true});
}

handleSearchBlur () {
this.setState({searchFocused: false});
}

componentDidMount () {
if (this.props.user) {
// Setup polling for messages to start in 2 minutes.
const twoMinInMs = 2 * 60 * 1000;
this.messageCountTimeoutId = setTimeout(this.pollForMessages.bind(this, twoMinInMs), twoMinInMs);
}
}

componentDidUpdate (prevProps) {
if (prevProps.user !== this.props.user) {
// If user changes (logs in/out), reset account nav and message polling
this.props.handleCloseAccountNav();
if (this.props.user) {
const twoMinInMs = 2 * 60 * 1000;
Expand All @@ -56,6 +73,7 @@ class Navigation extends React.Component {
}
}
}

componentWillUnmount () {
// clear message interval if it exists
if (this.messageCountTimeoutId !== -1) {
Expand All @@ -64,6 +82,7 @@ class Navigation extends React.Component {
this.messageCountTimeoutId = -1;
}
}

getProfileUrl () {
if (!this.props.user) return;
return `/users/${this.props.user.username}/`;
Expand All @@ -85,49 +104,51 @@ class Navigation extends React.Component {

handleSearchSubmit (formData) {
if (formData.q.trim() === '') return; // don't submit empty searches

let targetUrl = '/search/projects';
if (formData.q) {
targetUrl += `?q=${encodeURIComponent(formData.q)}`;
}
window.location.href = targetUrl;
}

render () {
const createLink = this.props.user ? '/projects/editor/' : '/projects/editor/?tutorial=getStarted';
return (
<NavigationBox
className={classNames({
'logged-in': this.props.user
'logged-in': this.props.user,
'search-focused': this.state.searchFocused
})}
>
<ul>
<li className="logo">
<a
aria-label="Scratch"
href="/"
/>
<a aria-label="Scratch" href="/" />
</li>

<li className="link create">
<a href={createLink}>
<FormattedMessage id="general.create" />
</a>
</li>
<li className="link explore">
<a href="/explore/projects/all">
<FormattedMessage id="general.explore" />
</a>
</li>
<li className="link ideas">
<a href="/ideas">
<FormattedMessage id="general.ideas" />
</a>
</li>
<li className="link about">
<a href="/about">
<FormattedMessage id="general.about" />
</a>
</li>
{!this.state.searchFocused && (
<>
<li className="link create">
<a href={createLink}>
<FormattedMessage id="general.create" />
</a>
</li>
<li className="link explore">
<a href="/explore/projects/all">
<FormattedMessage id="general.explore" />
</a>
</li>
<li className="link ideas">
<a href="/ideas">
<FormattedMessage id="general.ideas" />
</a>
</li>
<li className="link about">
<a href="/about">
<FormattedMessage id="general.about" />
</a>
</li>
</>
)}

<li className="search">
<Form onSubmit={this.handleSearchSubmit}>
Expand All @@ -138,20 +159,21 @@ class Navigation extends React.Component {
/>
<Input
aria-label={this.props.intl.formatMessage({id: 'general.search'})}
className="search-wrapper"
className={classNames('search-wrapper', {focused: this.state.searchFocused})}
name="q"
placeholder={this.props.intl.formatMessage({id: 'general.search'})}
type="text"
value={this.props.searchTerm}
onFocus={this.handleSearchFocus}
onBlur={this.handleSearchBlur}
/>
</Form>
</li>

{this.props.session.status === sessionActions.Status.FETCHED ? (
this.props.user ? [
<li
className="link right messages"
key="messages"
>
// --- Logged-in user view ---
<li className="link right messages" key="messages">
<a
href="/messages/"
title={this.props.intl.formatMessage({id: 'general.messages'})}
Expand All @@ -165,21 +187,15 @@ class Navigation extends React.Component {
<FormattedMessage id="general.messages" />
</a>
</li>,
<li
className="link right mystuff"
key="mystuff"
>
<li className="link right mystuff" key="mystuff">
<a
href="/mystuff/"
title={this.props.intl.formatMessage({id: 'general.myStuff'})}
>
<FormattedMessage id="general.myStuff" />
</a>
</li>,
<li
className="link right account-nav"
key="account-nav"
>
<li className="link right account-nav" key="account-nav">
<AccountNav
classroomId={this.props.user.classroomId}
isEducator={this.props.permissions.educator}
Expand All @@ -194,10 +210,8 @@ class Navigation extends React.Component {
/>
</li>
] : [
<li
className="link right join"
key="join"
>
// --- Logged-out user view ---
<li className="link right join" key="join">
{/* there's no css class registrationLink -- this is
just to make the link findable for testing */}
<a
Expand All @@ -208,10 +222,7 @@ class Navigation extends React.Component {
<FormattedMessage id="general.joinScratch" />
</a>
</li>,
<li
className="link right login-item"
key="login"
>
<li className="link right login-item" key="login">
<a
className="ignore-react-onclickoutside"
href="#"
Expand All @@ -220,16 +231,13 @@ class Navigation extends React.Component {
>
<FormattedMessage id="general.signIn" />
</a>
<LoginDropdown
key="login-dropdown"
/>
<LoginDropdown key="login-dropdown" />
</li>
]) : []
}

{this.props.registrationOpen && !this.props.useScratch3Registration && (
<Registration
key="registration"
/>
<Registration key="registration" />
)}
</ul>
<CanceledDeletionModal />
Expand Down Expand Up @@ -315,9 +323,7 @@ const mapDispatchToProps = dispatch => ({
});

// Allow incoming props to override redux-provided props. Used to mock in tests.
const mergeProps = (stateProps, dispatchProps, ownProps) => Object.assign(
{}, stateProps, dispatchProps, ownProps
);
const mergeProps = (stateProps, dispatchProps, ownProps) => Object.assign({}, stateProps, dispatchProps, ownProps);

const ConnectedNavigation = connect(
mapStateToProps,
Expand Down
26 changes: 14 additions & 12 deletions src/components/navigation/www/navigation.scss
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
@use "../../../frameless";

#navigation {
// Temporary removal of staging styling for testing purposes
// Temporary removal of staging styling for testing purposes
// &.staging {
// .messages {
// .message-count {
Expand Down Expand Up @@ -32,7 +32,6 @@
height: 50px;

&:hover {
transition: .15s ease all;
background-size: 100%;
}
}
Expand Down Expand Up @@ -69,7 +68,6 @@
height: 14px;

&[type=text] {
transition: .15s ease background-color;
padding: 0;
padding-right: 10px;
padding-left: 40px;
Expand All @@ -84,7 +82,6 @@
}

&:focus {
transition: .15s ease background-color;
background-color: colors.$active-dark-gray;
}

Expand All @@ -96,14 +93,12 @@

.btn-search {
position: absolute;

box-shadow: none;
background-color: transparent;
background-image: url("/images/nav-search-glass.png");
background-repeat: no-repeat;
background-position: center center;
background-size: 14px 14px;

width: 40px;
height: 40px;

Expand Down Expand Up @@ -131,7 +126,6 @@
background-size: 50%;
}
}

}

.messages {
Expand Down Expand Up @@ -164,9 +158,21 @@
background-image: url("/images/mystuff.png");
}
}

&.search-focused {
.inner > ul > li:not(.search) {
display: none !important;
}

.inner > ul > li.search {
flex-grow: 1 !important;
width: 100% !important;
margin: 0 !important;
}
}
}

//4 columns
// --- Responsive widths ---
@media #{frameless.$small} {
#navigation .inner {
width: frameless.$cols4;
Expand All @@ -189,8 +195,6 @@
}
}


//6 columns
@media #{frameless.$medium} {
#navigation .inner {
width: frameless.$cols6;
Expand All @@ -208,11 +212,9 @@
.profile-name {
display: none;
}

}
}

//8 columns
@media #{frameless.$intermediate} {
#navigation .inner {
width: frameless.$cols8;
Expand Down