Skip to content
This repository has been archived by the owner on Feb 29, 2020. It is now read-only.

Commit

Permalink
Fix Bug 1480507 - Add/Edit new Top Search Shortcut
Browse files Browse the repository at this point in the history
  • Loading branch information
rlr committed Aug 10, 2018
1 parent 29d5513 commit df19f2d
Show file tree
Hide file tree
Showing 12 changed files with 379 additions and 13 deletions.
3 changes: 3 additions & 0 deletions common/Actions.jsm
Original file line number Diff line number Diff line change
Expand Up @@ -99,15 +99,18 @@ for (const type of [
"TELEMETRY_UNDESIRED_EVENT",
"TELEMETRY_USER_EVENT",
"TOP_SITES_CANCEL_EDIT",
"TOP_SITES_CLOSE_SEARCH_SHORTCUTS_MODAL",
"TOP_SITES_EDIT",
"TOP_SITES_INSERT",
"TOP_SITES_OPEN_SEARCH_SHORTCUTS_MODAL",
"TOP_SITES_PIN",
"TOP_SITES_PREFS_UPDATED",
"TOP_SITES_UNPIN",
"TOP_SITES_UPDATED",
"TOTAL_BOOKMARKS_REQUEST",
"TOTAL_BOOKMARKS_RESPONSE",
"UNINIT",
"UPDATE_SEARCH_SHORTCUTS",
"UPDATE_SECTION_PREFS",
"WEBEXT_CLICK",
"WEBEXT_DISMISS"
Expand Down
12 changes: 11 additions & 1 deletion common/Reducers.jsm
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,11 @@ const INITIAL_STATE = {
// The history (and possibly default) links
rows: [],
// Used in content only to dispatch action to TopSiteForm.
editForm: null
editForm: null,
// Used in content only to open the SearchShortcutsForm modal.
showSearchShortcutsForm: false,
// The list of available search shortcuts.
searchShortcuts: []
},
Prefs: {
initialized: false,
Expand Down Expand Up @@ -98,6 +102,10 @@ function TopSites(prevState = INITIAL_STATE.TopSites, action) {
});
case at.TOP_SITES_CANCEL_EDIT:
return Object.assign({}, prevState, {editForm: null});
case at.TOP_SITES_OPEN_SEARCH_SHORTCUTS_MODAL:
return Object.assign({}, prevState, {showSearchShortcutsForm: true});
case at.TOP_SITES_CLOSE_SEARCH_SHORTCUTS_MODAL:
return Object.assign({}, prevState, {showSearchShortcutsForm: false});
case at.PREVIEW_RESPONSE:
if (!prevState.editForm || action.data.url !== prevState.editForm.previewUrl) {
return prevState;
Expand Down Expand Up @@ -172,6 +180,8 @@ function TopSites(prevState = INITIAL_STATE.TopSites, action) {
}
newRows = prevState.rows.filter(site => action.data.url !== site.url);
return Object.assign({}, prevState, {rows: newRows});
case at.UPDATE_SEARCH_SHORTCUTS:
return {...prevState, searchShortcuts: action.data.searchShortcuts};
default:
return prevState;
}
Expand Down
139 changes: 139 additions & 0 deletions content-src/components/TopSites/SearchShortcutsForm.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
import {actionCreators as ac, actionTypes as at} from "common/Actions.jsm";
import {FormattedMessage} from "react-intl";
import React from "react";

class SelectableSearchShortcut extends React.PureComponent {
render() {
const {shortcut, selected} = this.props;
const imageStyle = {backgroundImage: `url("${shortcut.tippyTopIcon}")`};
return (
<div className="top-site-outer">
<input type="checkbox" id={shortcut.keyword} name={shortcut.keyword} checked={selected} onChange={this.props.onChange} />
<label htmlFor={shortcut.keyword}>
<div className="top-site-inner">
<span>
<div className="tile">
<div className="top-site-icon rich-icon" style={imageStyle} data-fallback="@" />
<div className="top-site-icon search-topsite" />
</div>
<div className="title">
<span dir="auto">{shortcut.keyword}</span>
</div>
</span>
</div>
</label>
</div>
);
}
}

export class SearchShortcutsForm extends React.PureComponent {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
this.onCancelButtonClick = this.onCancelButtonClick.bind(this);
this.onSaveButtonClick = this.onSaveButtonClick.bind(this);

// clone the shortcuts and add them to the state so we can add isSelected property
const shortcuts = [];
const {rows, searchShortcuts} = props.TopSites;
searchShortcuts.forEach(shortcut => {
shortcuts.push(Object.assign({}, shortcut, {isSelected: !!rows.find(({isPinned, searchTopSite, label}) => isPinned && searchTopSite && label === shortcut.keyword)}));
});
this.state = {shortcuts};
}

handleChange(event) {
const {target} = event;
const {name, checked} = target;
this.setState(prevState => {
const shortcuts = prevState.shortcuts.slice();
let shortcut = shortcuts.find(({keyword}) => keyword === name);
shortcut.isSelected = checked;
return {shortcuts};
});
}

onCancelButtonClick(ev) {
ev.preventDefault();
this.props.onClose();
}

onSaveButtonClick(ev) {
ev.preventDefault();

// Check if there were any changes and act accordingly
const {rows} = this.props.TopSites;
const pinQueue = [];
const unpinQueue = [];
this.state.shortcuts.forEach(shortcut => {
const alreadyPinned = rows.find(({isPinned, searchTopSite, label}) => isPinned && searchTopSite && label === shortcut.keyword);
if (shortcut.isSelected && !alreadyPinned) {
pinQueue.push(this._searchTopSite(shortcut));
} else if (!shortcut.isSelected && alreadyPinned) {
unpinQueue.push({url: alreadyPinned.url});
}
});

// Pin the pinQueue
if (pinQueue.length > 0) {
// First find the available slots. A slot is available if it isn't pinned
// or if it's a pinned shortcut that we are about to unpin.
const availableSlots = [];
rows.forEach(({isPinned, searchTopSite, url}, index) => {
if (!isPinned || (searchTopSite && unpinQueue.find(site => url === site.url))) {
availableSlots.push(index);
}
});

pinQueue.forEach(shortcut => {
this.props.dispatch(ac.OnlyToMain({
type: at.TOP_SITES_PIN,
data: {
site: shortcut,
index: availableSlots.shift()
}
}));
});
}

// Unpin the unpinQueue.
unpinQueue.forEach(shortcut => {
this.props.dispatch(ac.OnlyToMain({
type: at.TOP_SITES_UNPIN,
data: {site: shortcut}
}));
});

this.props.onClose();
}

_searchTopSite(shortcut) {
return {
url: shortcut.url,
searchTopSite: true,
label: shortcut.keyword
};
}

render() {
return (
<form className="topsite-form">
<div className="search-shortcuts-container">
<h3 className="section-title">
<FormattedMessage id="section_menu_action_add_search_engine" />
</h3>
{this.state.shortcuts.map(shortcut => <SelectableSearchShortcut key={shortcut.keyword} shortcut={shortcut} selected={shortcut.isSelected} onChange={this.handleChange} />)}
</div>
<section className="actions">
<button className="cancel" type="button" onClick={this.onCancelButtonClick}>
<FormattedMessage id="topsites_form_cancel_button" />
</button>
<button className="done" type="submit" onClick={this.onSaveButtonClick}>
<FormattedMessage id="topsites_form_save_button" />
</button>
</section>
</form>
);
}
}
37 changes: 31 additions & 6 deletions content-src/components/TopSites/TopSites.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {ComponentPerfTimer} from "content-src/components/ComponentPerfTimer/Comp
import {connect} from "react-redux";
import {injectIntl} from "react-intl";
import React from "react";
import {SearchShortcutsForm} from "./SearchShortcutsForm";
import {TOP_SITES_MAX_SITES_PER_ROW} from "common/Reducers.jsm";
import {TopSiteForm} from "./TopSiteForm";
import {TopSiteList} from "./TopSite";
Expand Down Expand Up @@ -52,7 +53,8 @@ function countTopSitesIconsTypes(topSites) {
export class _TopSites extends React.PureComponent {
constructor(props) {
super(props);
this.onFormClose = this.onFormClose.bind(this);
this.onEditFormClose = this.onEditFormClose.bind(this);
this.onSearchShortcutsFormClose = this.onSearchShortcutsFormClose.bind(this);
}

/**
Expand Down Expand Up @@ -90,25 +92,37 @@ export class _TopSites extends React.PureComponent {
this._dispatchTopSitesStats();
}

onFormClose() {
onEditFormClose() {
this.props.dispatch(ac.UserEvent({
source: TOP_SITES_SOURCE,
event: "TOP_SITES_EDIT_CLOSE"
}));
this.props.dispatch({type: at.TOP_SITES_CANCEL_EDIT});
}

onSearchShortcutsFormClose() {
this.props.dispatch(ac.UserEvent({
source: TOP_SITES_SOURCE,
event: "TOP_SITES_SEARCH_SHORTCUTS_CLOSE"
}));
this.props.dispatch({type: at.TOP_SITES_CLOSE_SEARCH_SHORTCUTS_MODAL});
}

render() {
const {props} = this;
const {editForm} = props.TopSites;
const {editForm, showSearchShortcutsForm} = props.TopSites;
const extraMenuOptions = ["AddTopSite"];
if (props.Prefs.values["improvesearch.topSiteSearchShortcuts"]) {
extraMenuOptions.push("AddSearchShortcut");
}

return (<ComponentPerfTimer id="topsites" initialized={props.TopSites.initialized} dispatch={props.dispatch}>
<CollapsibleSection
className="top-sites"
icon="topsites"
id="topsites"
title={{id: "header_top_sites"}}
extraMenuOptions={["AddTopSite"]}
extraMenuOptions={extraMenuOptions}
showPrefName="feeds.topsites"
eventSource={TOP_SITES_SOURCE}
collapsed={props.TopSites.pref ? props.TopSites.pref.collapsed : undefined}
Expand All @@ -119,17 +133,28 @@ export class _TopSites extends React.PureComponent {
<div className="edit-topsites-wrapper">
{editForm &&
<div className="edit-topsites">
<div className="modal-overlay" onClick={this.onFormClose} />
<div className="modal-overlay" onClick={this.onEditFormClose} />
<div className="modal">
<TopSiteForm
site={props.TopSites.rows[editForm.index]}
onClose={this.onFormClose}
onClose={this.onEditFormClose}
dispatch={this.props.dispatch}
intl={this.props.intl}
{...editForm} />
</div>
</div>
}
{showSearchShortcutsForm &&
<div className="edit-search-shortcuts">
<div className="modal-overlay" onClick={this.onSearchShortcutsFormClose} />
<div className="modal">
<SearchShortcutsForm
TopSites={props.TopSites}
onClose={this.onSearchShortcutsFormClose}
dispatch={this.props.dispatch} />
</div>
</div>
}
</div>
</CollapsibleSection>
</ComponentPerfTimer>);
Expand Down
Loading

0 comments on commit df19f2d

Please sign in to comment.