Skip to content
This repository has been archived by the owner on Jan 17, 2023. It is now read-only.

Commit

Permalink
Enable favorite shots on My Shots. (#4934)
Browse files Browse the repository at this point in the history
  • Loading branch information
chenba committed Oct 3, 2018
1 parent 4290950 commit 166466d
Show file tree
Hide file tree
Showing 6 changed files with 65 additions and 1 deletion.
2 changes: 2 additions & 0 deletions docs/METRICS.md
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,8 @@ These are events that an add-on user can encounter on a shot they own
6. [x] Right-click (or get the context menu) anywhere on the page `contextmenu/background`, `contextmenu/shot-tile`, `contextmenu/search`, or `contextmenu/header` depending on where the user clicks.
7. [x] Click download from tile: `web/download/myshots-tile`
8. [x] Clear search with button: `web/clear-search/button`
8. [x] Click to favorite: `web/favorite/myshots-tile`
8. [x] Click to un-favorite: `web/unfavorite/myshots-tile`

#### Settings (My Shots)

Expand Down
1 change: 1 addition & 0 deletions locales/en-US/server.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,7 @@ shotIndexFavoriteIcon =
.title = This is a favorite shot and it does not expire
shotIndexSyncedShot =
.title = Shot taken on another device
shotIndexAlertErrorFavoriteShot = Error updating favorite shot status
## Delete Confirmation Dialog

Expand Down
43 changes: 43 additions & 0 deletions server/src/pages/shotindex/controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ exports.launch = function(m) {
m.shots = m.shots.map((shot) => {
const s = new AbstractShot(m.backend, shot.id, shot.json);
s.expireTime = shot.expireTime;
s.isFavorite = !shot.expireTime;
s.isSynced = shot.isSynced;
return s;
});
Expand Down Expand Up @@ -173,6 +174,48 @@ exports.deleteShot = function(shot) {
req.send(`id=${encodeURIComponent(shot.id)}&_csrf=${encodeURIComponent(model.csrfToken)}`);
};


exports.toggleFavoriteShot = async function(shot) {
const url = model.backend + "/api/set-expiration";
const newExpiration = shot.isFavorite ? model.defaultExpirationMs : 0;
const body = new URLSearchParams();

body.append("id", shot.id);
body.append("expiration", newExpiration);
body.append("_csrf", model.csrfToken);

try {
const resp = await fetch(url, {
method: "POST",
body,
});

if (!resp.ok) {
const exc = new Error(`Error calling /api/set-expiration: ${resp.status} ${resp.statusText}`);
window.Raven.captureException(exc);
throw exc;
}

if (newExpiration) {
shot.expireTime = Date.now() + newExpiration;
} else {
shot.expireTime = null;
}

render();
} catch (err) {
console.warn("Error updating expiration:", err);

// The error could be one from the server or a network level error from
// fetch; we display a generic message either way as it's (probably?)
// friendlier than percolating a fetch error message up to the user.
const errorMessage = document.getElementById("shotIndexAlertErrorFavoriteShot").textContent;
if (errorMessage) {
window.alert(errorMessage);
}
}
};

window.addEventListener("popstate", () => {
const match = /[?&]q=([^&]{0,4000})/.exec(location.search);
if (!match) {
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 @@ -18,6 +18,7 @@ exports.createModel = function(req) {
serverModel.downloadUrls = {};
serverModel.disableSearch = req.config.disableSearch;
serverModel.enableUserSettings = req.config.enableUserSettings;
serverModel.defaultExpirationMs = req.config.defaultExpiration * 1000;
let shots = req.shots;
for (const shot of shots || []) {
const clip = shot.getClip(shot.clipNames()[0]);
Expand Down
17 changes: 16 additions & 1 deletion server/src/pages/shotindex/view.js
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,9 @@ class Body extends React.Component {
renderErrorMessages() {
return (
<div>
<Localized id="shotIndexAlertErrorFavoriteShot">
<div id="shotIndexAlertErrorFavoriteShot" hidden></div>
</Localized>,
<Localized id="shotIndexPageErrorDeletingShot">
<div id="shotIndexPageErrorDeletingShot" hidden></div>
</Localized>
Expand Down Expand Up @@ -319,11 +322,13 @@ class Card extends React.Component {
if (!shot.expireTime) {
favoriteIndicator = <Localized id="shotIndexFavoriteIcon" attrs={{title: true}}>
<div className={classnames("indicator fav-shot", {"inactive": !this.props.hasFxa})}
onClick={this.props.hasFxa ? this.onClickFavorite.bind(this, shot) : null}
title=""></div>
</Localized>;
} else if (this.props.hasFxa) {
favoriteIndicator = <Localized id="shotIndexNonFavoriteIcon" attrs={{title: true}}>
<div className="indicator non-fav-shot" title=""></div>
<div className="indicator non-fav-shot"
onClick={this.onClickFavorite.bind(this, shot)} title=""></div>
</Localized>;
}

Expand Down Expand Up @@ -463,6 +468,16 @@ class Card extends React.Component {
this.downloadButton.blur();
sendEvent("download", "myshots-tile");
}

onClickFavorite(shot) {
controller.toggleFavoriteShot(shot);

if (shot.isFavorite) {
sendEvent("favorite", "myshots-tile");
} else {
sendEvent("unfavorite", "myshots-tile");
}
}
}

Card.propTypes = {
Expand Down
2 changes: 2 additions & 0 deletions static/css/shot-index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,7 @@ $shot-width: 210px;

.fav-shot,
.non-fav-shot {
cursor: pointer;
right: 12px;
top: 8px;
background-size: 23px 20px;
Expand All @@ -231,6 +232,7 @@ $shot-width: 210px;
.fav-shot {
background-image: url("../img/icon-heart.svg");
&.inactive {
cursor: default;
opacity: 0.4;
}
}
Expand Down

0 comments on commit 166466d

Please # to comment.