Skip to content

Commit

Permalink
Replace shot expiration with favorites for FxA users. (mozilla-servic…
Browse files Browse the repository at this point in the history
  • Loading branch information
chenba committed Jul 31, 2018
1 parent 6180b83 commit 1a47917
Show file tree
Hide file tree
Showing 8 changed files with 76 additions and 124 deletions.
7 changes: 5 additions & 2 deletions server/src/pages/shot/model.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ exports.createModel = function(req) {
}
const title = req.getText("shotPageTitle", {originalTitle: req.shot.title});
const enableAnnotations = req.config.enableAnnotations;
const isFxaAuthenticated = req.accountId && req.accountId === req.shot.accountId;
const serverPayload = {
title,
staticLink: req.staticLink,
Expand All @@ -25,7 +26,8 @@ exports.createModel = function(req) {
id: req.shot.id,
productName: req.config.productName,
isExtInstalled: !!req.deviceId,
isOwner: req.deviceId === req.shot.ownerId || (req.accountId && req.accountId === req.shot.accountId),
isOwner: req.deviceId === req.shot.ownerId || isFxaAuthenticated,
isFxaAuthenticated,
gaId: req.config.gaId,
deviceId: req.deviceId,
authenticated: !!req.deviceId,
Expand Down Expand Up @@ -54,7 +56,8 @@ exports.createModel = function(req) {
id: req.shot.id,
productName: req.config.productName,
isExtInstalled: !!req.deviceId,
isOwner: req.deviceId === req.shot.ownerId || (req.accountId && req.accountId === req.shot.accountId),
isOwner: req.deviceId === req.shot.ownerId || isFxaAuthenticated,
isFxaAuthenticated,
gaId: req.config.gaId,
deviceId: req.deviceId,
authenticated: !!req.deviceId,
Expand Down
146 changes: 28 additions & 118 deletions server/src/pages/shot/view.js
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,6 @@ class Body extends React.Component {
this.state = {
hidden: false,
closeBanner: false,
isChangingExpire: false,
imageEditing: false
};
}
Expand Down Expand Up @@ -340,19 +339,21 @@ class Body extends React.Component {
const linkTextShort = shot.urlDisplay;

const timeDiff = <TimeDiff date={shot.createdDate} />;
let expiresDiff = null;
if (this.props.isOwner) {
expiresDiff = <span className="expire-widget">
<ExpireWidget
expireTime={this.props.expireTime}
onChanging={this.onChangingExpire.bind(this)}
onSaveExpire={this.onSaveExpire.bind(this)} />
</span>;
}

let favoriteShotButton = null;
let trashOrFlagButton;
let editButton;
const highlight = this.state.highlightEditButton ? <div className="edit-highlight" onClick={ this.onClickEdit.bind(this) } onMouseOver={ this.onMouseOverHighlight.bind(this) } onMouseOut={ this.onMouseOutHighlight.bind(this) }></div> : null;

if (this.props.isFxaAuthenticated) {
const activeFavClass = this.props.expireTime ? "" : "is-fav";
favoriteShotButton = <div className="fav-wrapper"><button
className={`button favorite ${activeFavClass}`}
title="Favorite this shot"
onClick={ this.onClickFavorite.bind(this) }
/></div>;
}

if (this.props.isOwner) {
trashOrFlagButton = <DeleteShotButton
clickDeleteHandler={ this.clickDeleteHandler.bind(this) }
Expand Down Expand Up @@ -418,14 +419,14 @@ class Body extends React.Component {
<div className="shot-info">
<EditableTitle title={shot.title} isOwner={this.props.isOwner} />
<div className="shot-subtitle">
{ this.state.isChangingExpire ? null : favicon }
{ linkTextShort && !this.state.isChangingExpire ? <a className="subtitle-link" rel="noopener noreferrer" href={ shot.url } target="_blank" onClick={ this.onClickOrigUrl.bind(this, "navbar") }>{ linkTextShort }</a> : null }
{ this.state.isChangingExpire ? null : <span className="time-diff">{ timeDiff }</span> }
{ expiresDiff }
{ favicon }
{ linkTextShort ? <a className="subtitle-link" rel="noopener noreferrer" href={ shot.url } target="_blank" onClick={ this.onClickOrigUrl.bind(this, "navbar") }>{ linkTextShort }</a> : null }
<span className="time-diff">{ timeDiff }</span>
</div>
</div>
</div>
<div className="shot-alt-actions">
{ favoriteShotButton }
{ trashOrFlagButton }
{ this.props.enableAnnotations ? editButton : null }
{ highlight }
Expand Down Expand Up @@ -498,20 +499,6 @@ class Body extends React.Component {
sendEvent("click-install-firefox-shot", {useBeacon: true});
}

onSaveExpire(value) {
sendEvent("set-expiration", "navbar");
if (value === 0 || value === "0") {
sendEvent("set-expiration-to-indefinite", "navbar");
} else {
sendEvent("set-expiration-to-time", "navbar");
}
this.props.controller.changeShotExpiration(this.props.shot, value);
}

onChangingExpire(value) {
this.setState({isChangingExpire: value});
}

onRestore() {
sendEvent("recover-expired");
this.props.controller.changeShotExpiration(this.props.shot, this.props.defaultExpiration);
Expand All @@ -535,6 +522,18 @@ class Body extends React.Component {
// Note: we allow the default action to continue
}

onClickFavorite() {
if (this.props.expireTime) {
sendEvent("set-favorite", "navbar");
const INDEFINITE = 0;
this.props.controller.changeShotExpiration(this.props.shot, INDEFINITE);
} else {
sendEvent("unset-favorite", "navbar");
const TWO_WEEKS_IN_SECONDS = 1209600;
this.props.controller.changeShotExpiration(this.props.shot, Date.now() + TWO_WEEKS_IN_SECONDS);
}
}

onClickDownload() {
sendEvent("download", "navbar");
}
Expand All @@ -559,6 +558,7 @@ Body.propTypes = {
isExtInstalled: PropTypes.bool,
isMobile: PropTypes.bool,
isOwner: PropTypes.bool,
isFxaAuthenticated: PropTypes.bool,
loginFailed: PropTypes.bool,
pngToJpegCutoff: PropTypes.number,
retentionTime: PropTypes.number,
Expand All @@ -569,96 +569,6 @@ Body.propTypes = {
userLocales: PropTypes.array
};

class ExpireWidget extends React.Component {

constructor(props) {
super(props);
this.state = {isChangingExpire: false};
}

render() {
if (this.state.isChangingExpire) {
return this.renderChanging();
}
return this.renderNormal();
}

renderChanging() {
const minute = 60 * 1000;
const hour = minute * 60;
const day = hour * 24;
return (
<span className="keep-for-form">
<Localized id="shotPageKeepFor"><span>How long should this shot be retained?</span></Localized>
<select ref={expireTime => this.expireTime = expireTime}>
<Localized id="shotPageSelectTime"><option value="cancel">Select time</option></Localized>
<Localized id="shotPageKeepIndefinitelyWithSymbol"><option value="0">Indefinitely &infin;</option></Localized>
<Localized id="shotPageKeepTenMinutes"><option value={ 10 * minute }>10 Minutes</option></Localized>
<Localized id="shotPageKeepOneHour"><option value={ hour }>1 Hour</option></Localized>
<Localized id="shotPageKeepOneDay"><option value={ day }>1 Day</option></Localized>
<Localized id="shotPageKeepOneWeek"><option value={ 7 * day }>1 Week</option></Localized>
<Localized id="shotPageKeepTwoWeeks"><option value={ 14 * day }>2 Weeks</option></Localized>
<Localized id="shotPageKeepOneMonth"><option value={ 31 * day }>1 Month</option></Localized>
</select>
<Localized id="shotPageSaveExpiration"><span className="button tiny secondary" tabIndex="0" onClick={this.clickSaveExpire.bind(this)}>save</span></Localized>
<Localized id="shotPageCancelExpiration"><span className="button tiny secondary" tabIndex="0" onClick={this.clickCancelExpire.bind(this)}>cancel</span></Localized>
</span>
);
}

renderNormal() {
let button;
if (this.props.expireTime === null) {
button = <Localized id="shotPageDoesNotExpire"><span>does not expire</span></Localized>;
} else {
const expired = this.props.expireTime < Date.now();
const timediff = <TimeDiff date={this.props.expireTime} simple={this.props.simple} />;
if (expired) {
button = <Localized id="shotPageExpired" $timediff={timediff}><span>expired {timediff}</span></Localized>;
} else {
button = <Localized id="shotPageExpiresIn" $timediff={timediff}><span>expires {timediff}</span></Localized>;
}
}
return (
<button className="button tiny secondary inline" onClick={this.clickChangeExpire.bind(this)}>
{button}
</button>
);
}

clickChangeExpire() {
sendEvent("start-expiration-change", "navbar");
this.setState({isChangingExpire: true});
this.props.onChanging(true);
}

clickCancelExpire() {
sendEvent("cancel-expiration-change", "navbar");
this.setState({isChangingExpire: false});
this.props.onChanging(false);
}

clickSaveExpire() {
// FIXME: save the value that it was changed to? Yes! Not sure where to put it.
let value = this.expireTime.value;
if (value === "cancel") {
this.clickCancelExpire();
return;
}
value = parseInt(value, 10);
// Note: sendEvent done in onSaveExpire
this.props.onSaveExpire(value);
this.props.onChanging(false);
this.setState({isChangingExpire: false});
}
}

ExpireWidget.propTypes = {
expireTime: PropTypes.number,
onChanging: PropTypes.func,
onSaveExpire: PropTypes.func,
simple: PropTypes.bool
};

class EditableTitle extends React.Component {

Expand Down
1 change: 1 addition & 0 deletions server/src/pages/shotindex/model.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ exports.createModel = function(req) {
}
const serverModel = {
title,
hasFxa: !!req.accountId,
hasDeviceId: req.deviceId !== undefined,
defaultSearch: query || null
};
Expand Down
10 changes: 8 additions & 2 deletions server/src/pages/shotindex/view.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ class Body extends React.Component {
const children = [];
if (this.props.shots && this.props.shots.length) {
for (const shot of this.props.shots) {
children.push(<Card shot={shot} downloadUrl={this.props.downloadUrls[shot.id]} abTests={this.props.abTests} clipUrl={shot.urlDisplay} isOwner={this.props.isOwner} staticLink={this.props.staticLink} isExtInstalled={this.props.isExtInstalled} key={shot.id} />);
children.push(<Card shot={shot} downloadUrl={this.props.downloadUrls[shot.id]} abTests={this.props.abTests} clipUrl={shot.urlDisplay} isOwner={this.props.isOwner} staticLink={this.props.staticLink} isExtInstalled={this.props.isExtInstalled} hasFxa={this.props.hasFxa} key={shot.id} />);
}
}

Expand Down Expand Up @@ -281,6 +281,7 @@ Body.propTypes = {
downloadUrls: PropTypes.object,
enableUserSettings: PropTypes.bool,
hasDeviceId: PropTypes.bool,
hasFxa: PropTypes.bool,
isExtInstalled: PropTypes.bool,
isOwner: PropTypes.bool,
pageNumber: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
Expand Down Expand Up @@ -329,7 +330,11 @@ class Card extends React.Component {

let neverExpireIndicator = null;
if (!shot.expireTime) {
neverExpireIndicator = <Localized id="shotIndexNoExpirationSymbol"><div className="never-expires" title=""></div></Localized>;
if (this.props.hasFxa) {
neverExpireIndicator = <Localized id="shotIndexFavoriteIcon"><div className="favorite-shot" title=""></div></Localized>;
} else {
neverExpireIndicator = <Localized id="shotIndexNoExpirationSymbol"><div className="never-expires" title=""></div></Localized>;
}
}

const deleteConfirmationClass = this.state.deletePanelOpen ? "panel-open" : "";
Expand Down Expand Up @@ -462,6 +467,7 @@ class Card extends React.Component {
Card.propTypes = {
abTests: PropTypes.object,
downloadUrl: PropTypes.string,
hasFxa: PropTypes.bool,
isExtInstalled: PropTypes.bool,
isOwner: PropTypes.bool,
shot: PropTypes.object,
Expand Down
2 changes: 1 addition & 1 deletion server/src/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -861,7 +861,7 @@ app.post("/api/save-edit", function(req, res) {
});

app.post("/api/set-expiration", function(req, res) {
if (!req.deviceId) {
if (!req.deviceId || !req.accountId) {
sendRavenMessage(req, "Attempt to set expiration without login");
simpleResponse(res, "Not logged in", 401);
return;
Expand Down
22 changes: 21 additions & 1 deletion static/css/frame.scss
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,26 @@
}
}

.fav-wrapper {
&:active,
&:hover {
background-color: $light-hover;
}
border-radius: $border-radius;
margin-right: $grid-unit * 0.5;
}
.favorite {
background: url("../img/icon-heart.svg");
background-size: 20px 20px;
background-position: center;
background-repeat: no-repeat;
filter: brightness(2.4);

&.is-fav {
filter: brightness(1);
}
}

.delete-confirmation-dialog {
&.right-align {
top: 48px;
Expand Down Expand Up @@ -517,7 +537,7 @@ body {
height: 40px;
border-radius: 3px;
position: absolute;
background-color: #ededf0;
background-color: $light-hover;
}

#color-picker {
Expand Down
11 changes: 11 additions & 0 deletions static/css/shot-index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ h1 {
.alt-actions-container {
opacity: 1;
}
.favorite-shot,
.never-expires {
display: none;
}
Expand Down Expand Up @@ -244,13 +245,23 @@ h1 {
margin: 12px;
}

.favorite-shot,
.never-expires {
bottom: 0;
color: #9e9e9e;
font-size: 1.2em;
position: absolute;
right: 8px;
}

.favorite-shot {
background-image: url("../img/icon-heart.svg");
background-position: center;
background-repeat: no-repeat;
background-size: 16px 16px;
width: 16px;
height: 26px;
}
}

.preferences {
Expand Down
1 change: 1 addition & 0 deletions static/img/icon-heart.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 1a47917

Please sign in to comment.