Merge branch 'develop' into master
This commit is contained in:
@ -20,7 +20,8 @@ class AlbumGrid extends React.Component{
|
||||
e: e,
|
||||
context: 'album',
|
||||
uris: [item.uri],
|
||||
items: [item]
|
||||
items: [item],
|
||||
tracklist_uri: item.uri
|
||||
}
|
||||
this.props.uiActions.showContextMenu(data)
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,7 +1,7 @@
|
||||
|
||||
import React, { PropTypes } from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
import { Link } from 'react-router'
|
||||
import Link from './Link'
|
||||
import { createStore, bindActionCreators } from 'redux'
|
||||
|
||||
import * as helpers from '../helpers'
|
||||
@ -51,7 +51,7 @@ class DebugInfo extends React.Component{
|
||||
|
||||
renderLoadQueue(){
|
||||
if (!this.props.ui.load_queue){
|
||||
return <div className="debug-info-item grey-text">Nothing loading</div>;
|
||||
return <div className="debug-info-item mid_grey-text">Nothing loading</div>;
|
||||
}
|
||||
|
||||
var load_queue = this.props.ui.load_queue
|
||||
@ -69,7 +69,7 @@ class DebugInfo extends React.Component{
|
||||
</div>
|
||||
)
|
||||
} else {
|
||||
return <div className="debug-info-item grey-text">Nothing loading</div>;
|
||||
return <div className="debug-info-item mid_grey-text">Nothing loading</div>;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,25 +1,40 @@
|
||||
|
||||
import React from 'react'
|
||||
import React from 'react';
|
||||
|
||||
export default class ErrorBoundary extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = { hasError: false };
|
||||
this.state = {
|
||||
hasError: false,
|
||||
error: null,
|
||||
info: null
|
||||
};
|
||||
}
|
||||
|
||||
componentDidCatch(error, info) {
|
||||
// Display fallback UI
|
||||
this.setState({ hasError: true });
|
||||
// You can also log the error to an error reporting service
|
||||
//logErrorToMyService(error, info);
|
||||
console.error(error,info);
|
||||
componentDidCatch(error, info){
|
||||
this.setState({
|
||||
hasError: true,
|
||||
error: error,
|
||||
info: info
|
||||
});
|
||||
console.error(error, info);
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.state.hasError){
|
||||
// You can render any custom fallback UI
|
||||
return <p className="grey-text">Failed to render</p>;
|
||||
return (
|
||||
<div className="error-boundary">
|
||||
|
||||
<h4 className="error-boundary__title">
|
||||
<i className="icon icon--material">error</i>
|
||||
{this.state.error ? this.state.error.toString() : "Unknown error"}
|
||||
</h4>
|
||||
|
||||
{this.state.info ? <pre className="error-boundary__trace">{this.state.info.componentStack}</pre> : null}
|
||||
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return this.props.children;
|
||||
}
|
||||
|
||||
@ -2,8 +2,9 @@
|
||||
import React, { PropTypes } from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
import { bindActionCreators } from 'redux'
|
||||
import { Link, hashHistory } from 'react-router'
|
||||
import { hashHistory } from 'react-router'
|
||||
|
||||
import Link from '../Link';
|
||||
import ArtistSentence from './../ArtistSentence'
|
||||
import * as helpers from '../../helpers'
|
||||
import * as coreActions from '../../services/core/actions'
|
||||
@ -84,13 +85,13 @@ class AddSeedField extends React.Component{
|
||||
|
||||
return (
|
||||
<div className="type">
|
||||
<h4 className="grey-text">{type}</h4>
|
||||
<h4 className="mid_grey-text">{type}</h4>
|
||||
{
|
||||
items.map(item => {
|
||||
return (
|
||||
<div className="result" key={item.uri} onClick={e => this.handleSelect(e,item)}>
|
||||
{item.name}
|
||||
{type == 'tracks' ? <span className="grey-text"> <ArtistSentence artists={item.artists} nolinks /></span> : null}
|
||||
{type == 'tracks' ? <span className="mid_grey-text"> <ArtistSentence artists={item.artists} nolinks /></span> : null}
|
||||
</div>
|
||||
)
|
||||
})
|
||||
|
||||
@ -2,7 +2,8 @@
|
||||
import React, { PropTypes } from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
import { bindActionCreators } from 'redux'
|
||||
import { Link, hashHistory } from 'react-router'
|
||||
import { hashHistory } from 'react-router'
|
||||
import Link from './Link'
|
||||
|
||||
import ArtistSentence from './../ArtistSentence'
|
||||
import * as helpers from '../../helpers'
|
||||
@ -96,13 +97,13 @@ class AddSeedField extends React.Component{
|
||||
|
||||
return (
|
||||
<div className="type">
|
||||
<h4 className="grey-text">{type}</h4>
|
||||
<h4 className="mid_grey-text">{type}</h4>
|
||||
{
|
||||
items.map(item => {
|
||||
return (
|
||||
<div className="result" key={item.uri} onClick={e => this.handleSelect(e,item)}>
|
||||
{item.name}
|
||||
{type == 'tracks' ? <span className="grey-text"> <ArtistSentence artists={item.artists} nolinks /></span> : null}
|
||||
{type == 'tracks' ? <span className="mid_grey-text"> <ArtistSentence artists={item.artists} nolinks /></span> : null}
|
||||
</div>
|
||||
)
|
||||
})
|
||||
|
||||
56
src/js/components/Fields/ColourField.js
Executable file
56
src/js/components/Fields/ColourField.js
Executable file
@ -0,0 +1,56 @@
|
||||
|
||||
import React from 'react';
|
||||
import Icon from '../Icon';
|
||||
|
||||
export default class ColourField extends React.Component{
|
||||
|
||||
constructor(props){
|
||||
super(props);
|
||||
}
|
||||
|
||||
render(){
|
||||
|
||||
var colours = [
|
||||
'',
|
||||
'white',
|
||||
'mid_grey',
|
||||
'grey',
|
||||
'dark_grey',
|
||||
'black',
|
||||
'turquoise',
|
||||
'green',
|
||||
'blue',
|
||||
'light_blue',
|
||||
'yellow',
|
||||
'orange',
|
||||
'red'
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="colour-field">
|
||||
{
|
||||
colours.map(colour => {
|
||||
switch (colour){
|
||||
case 'yellow':
|
||||
case 'white':
|
||||
case 'light_blue':
|
||||
var text_colour = "black";
|
||||
break;
|
||||
default:
|
||||
var text_colour = "white";
|
||||
break;
|
||||
}
|
||||
return (
|
||||
<div
|
||||
key={colour}
|
||||
className={"colour-field__option "+(colour ? colour+"-background " : "")+(this.props.colour == colour ? "colour-field__option--selected" : "")}
|
||||
onClick={e => this.props.onChange(colour)}>
|
||||
{this.props.colour == colour ? <Icon name="check" className={"colour-field__option__icon "+text_colour+"-text"} /> : null}
|
||||
</div>
|
||||
);
|
||||
})
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -118,7 +118,7 @@ export default class DropdownField extends React.Component{
|
||||
options.push({
|
||||
value: 'select-all',
|
||||
label: 'Select all',
|
||||
className: 'grey-text'
|
||||
className: 'mid_grey-text'
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
|
||||
import React, { PropTypes } from 'react'
|
||||
import { Link } from 'react-router'
|
||||
|
||||
import Link from '../Link';
|
||||
import Icon from '../Icon';
|
||||
|
||||
export default class FilterField extends React.Component{
|
||||
@ -17,11 +17,12 @@ export default class FilterField extends React.Component{
|
||||
}
|
||||
|
||||
componentWillMount(){
|
||||
window.addEventListener("keyup", this.handleKeyUp, false)
|
||||
window.addEventListener("keyup", this.handleKeyUp, false);
|
||||
this.setState({value: this.props.initialValue});
|
||||
}
|
||||
|
||||
componentWillUnmount(){
|
||||
window.removeEventListener("keyup", this.handleKeyUp, false)
|
||||
window.removeEventListener("keyup", this.handleKeyUp, false);
|
||||
}
|
||||
|
||||
handleKeyUp(e){
|
||||
@ -59,7 +60,6 @@ export default class FilterField extends React.Component{
|
||||
return (
|
||||
<span className={"filter-field "+(this.state.active ? 'active' : '')} onClick={e => this.activate()}>
|
||||
<form>
|
||||
<Icon name="search" type="material" />
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Filter"
|
||||
@ -68,6 +68,7 @@ export default class FilterField extends React.Component{
|
||||
onBlur={e => this.handleBlur()}
|
||||
onChange={e => this.handleChange(e.target.value)}
|
||||
/>
|
||||
<Icon name="search" type="material" />
|
||||
</form>
|
||||
</span>
|
||||
)
|
||||
|
||||
@ -1,9 +1,10 @@
|
||||
|
||||
import React, { PropTypes } from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
import { Link } from 'react-router'
|
||||
import { createStore, bindActionCreators } from 'redux'
|
||||
|
||||
import Link from '../Link';
|
||||
|
||||
import * as helpers from '../../helpers'
|
||||
import * as uiActions from '../../services/ui/actions'
|
||||
import * as spotifyActions from '../../services/spotify/actions'
|
||||
|
||||
971
src/js/components/Fields/IconField.js
Executable file
971
src/js/components/Fields/IconField.js
Executable file
@ -0,0 +1,971 @@
|
||||
|
||||
import React from 'react';
|
||||
import Icon from '../Icon';
|
||||
|
||||
export default class ColourField extends React.Component{
|
||||
|
||||
constructor(props){
|
||||
super(props);
|
||||
}
|
||||
|
||||
icons(){
|
||||
if (this.props.icons){
|
||||
return this.props.icons;
|
||||
} else {
|
||||
return [
|
||||
"3d_rotation",
|
||||
"ac_unit",
|
||||
"access_alarm",
|
||||
"access_alarms",
|
||||
"access_time",
|
||||
"accessibility",
|
||||
"accessible",
|
||||
"account_balance",
|
||||
"account_balance_wallet",
|
||||
"account_box",
|
||||
"account_circle",
|
||||
"adb",
|
||||
"add",
|
||||
"add_a_photo",
|
||||
"add_alarm",
|
||||
"add_alert",
|
||||
"add_box",
|
||||
"add_circle",
|
||||
"add_circle_outline",
|
||||
"add_location",
|
||||
"add_shopping_cart",
|
||||
"add_to_photos",
|
||||
"add_to_queue",
|
||||
"adjust",
|
||||
"airline_seat_flat",
|
||||
"airline_seat_flat_angled",
|
||||
"airline_seat_individual_suite",
|
||||
"airline_seat_legroom_extra",
|
||||
"airline_seat_legroom_normal",
|
||||
"airline_seat_legroom_reduced",
|
||||
"airline_seat_recline_extra",
|
||||
"airline_seat_recline_normal",
|
||||
"airplanemode_active",
|
||||
"airplanemode_inactive",
|
||||
"airplay",
|
||||
"airport_shuttle",
|
||||
"alarm",
|
||||
"alarm_add",
|
||||
"alarm_off",
|
||||
"alarm_on",
|
||||
"album",
|
||||
"all_inclusive",
|
||||
"all_out",
|
||||
"android",
|
||||
"announcement",
|
||||
"apps",
|
||||
"archive",
|
||||
"arrow_back",
|
||||
"arrow_downward",
|
||||
"arrow_drop_down",
|
||||
"arrow_drop_down_circle",
|
||||
"arrow_drop_up",
|
||||
"arrow_forward",
|
||||
"arrow_upward",
|
||||
"art_track",
|
||||
"aspect_ratio",
|
||||
"assessment",
|
||||
"assignment",
|
||||
"assignment_ind",
|
||||
"assignment_late",
|
||||
"assignment_return",
|
||||
"assignment_returned",
|
||||
"assignment_turned_in",
|
||||
"assistant",
|
||||
"assistant_photo",
|
||||
"attach_file",
|
||||
"attach_money",
|
||||
"attachment",
|
||||
"audiotrack",
|
||||
"autorenew",
|
||||
"av_timer",
|
||||
"backspace",
|
||||
"backup",
|
||||
"battery_alert",
|
||||
"battery_charging_full",
|
||||
"battery_full",
|
||||
"battery_std",
|
||||
"battery_unknown",
|
||||
"beach_access",
|
||||
"beenhere",
|
||||
"block",
|
||||
"bluetooth",
|
||||
"bluetooth_audio",
|
||||
"bluetooth_connected",
|
||||
"bluetooth_disabled",
|
||||
"bluetooth_searching",
|
||||
"blur_circular",
|
||||
"blur_linear",
|
||||
"blur_off",
|
||||
"blur_on",
|
||||
"book",
|
||||
"bookmark",
|
||||
"bookmark_border",
|
||||
"border_all",
|
||||
"border_bottom",
|
||||
"border_clear",
|
||||
"border_color",
|
||||
"border_horizontal",
|
||||
"border_inner",
|
||||
"border_left",
|
||||
"border_outer",
|
||||
"border_right",
|
||||
"border_style",
|
||||
"border_top",
|
||||
"border_vertical",
|
||||
"branding_watermark",
|
||||
"brightness_1",
|
||||
"brightness_2",
|
||||
"brightness_3",
|
||||
"brightness_4",
|
||||
"brightness_5",
|
||||
"brightness_6",
|
||||
"brightness_7",
|
||||
"brightness_auto",
|
||||
"brightness_high",
|
||||
"brightness_low",
|
||||
"brightness_medium",
|
||||
"broken_image",
|
||||
"brush",
|
||||
"bubble_chart",
|
||||
"bug_report",
|
||||
"build",
|
||||
"burst_mode",
|
||||
"business",
|
||||
"business_center",
|
||||
"cached",
|
||||
"cake",
|
||||
"call",
|
||||
"call_end",
|
||||
"call_made",
|
||||
"call_merge",
|
||||
"call_missed",
|
||||
"call_missed_outgoing",
|
||||
"call_received",
|
||||
"call_split",
|
||||
"call_to_action",
|
||||
"camera",
|
||||
"camera_alt",
|
||||
"camera_enhance",
|
||||
"camera_front",
|
||||
"camera_rear",
|
||||
"camera_roll",
|
||||
"cancel",
|
||||
"card_giftcard",
|
||||
"card_membership",
|
||||
"card_travel",
|
||||
"casino",
|
||||
"cast",
|
||||
"cast_connected",
|
||||
"center_focus_strong",
|
||||
"center_focus_weak",
|
||||
"change_history",
|
||||
"chat",
|
||||
"chat_bubble",
|
||||
"chat_bubble_outline",
|
||||
"check",
|
||||
"check_box",
|
||||
"check_box_outline_blank",
|
||||
"check_circle",
|
||||
"chevron_left",
|
||||
"chevron_right",
|
||||
"child_care",
|
||||
"child_friendly",
|
||||
"chrome_reader_mode",
|
||||
"class",
|
||||
"clear",
|
||||
"clear_all",
|
||||
"close",
|
||||
"closed_caption",
|
||||
"cloud",
|
||||
"cloud_circle",
|
||||
"cloud_done",
|
||||
"cloud_download",
|
||||
"cloud_off",
|
||||
"cloud_queue",
|
||||
"cloud_upload",
|
||||
"code",
|
||||
"collections",
|
||||
"collections_bookmark",
|
||||
"color_lens",
|
||||
"colorize",
|
||||
"comment",
|
||||
"compare",
|
||||
"compare_arrows",
|
||||
"computer",
|
||||
"confirmation_number",
|
||||
"contact_mail",
|
||||
"contact_phone",
|
||||
"contacts",
|
||||
"content_copy",
|
||||
"content_cut",
|
||||
"content_paste",
|
||||
"control_point",
|
||||
"control_point_duplicate",
|
||||
"copyright",
|
||||
"create",
|
||||
"create_new_folder",
|
||||
"credit_card",
|
||||
"crop",
|
||||
"crop_16_9",
|
||||
"crop_3_2",
|
||||
"crop_5_4",
|
||||
"crop_7_5",
|
||||
"crop_din",
|
||||
"crop_free",
|
||||
"crop_landscape",
|
||||
"crop_original",
|
||||
"crop_portrait",
|
||||
"crop_rotate",
|
||||
"crop_square",
|
||||
"dashboard",
|
||||
"data_usage",
|
||||
"date_range",
|
||||
"dehaze",
|
||||
"delete",
|
||||
"delete_forever",
|
||||
"delete_sweep",
|
||||
"description",
|
||||
"desktop_mac",
|
||||
"desktop_windows",
|
||||
"details",
|
||||
"developer_board",
|
||||
"developer_mode",
|
||||
"device_hub",
|
||||
"devices",
|
||||
"devices_other",
|
||||
"dialer_sip",
|
||||
"dialpad",
|
||||
"directions",
|
||||
"directions_bike",
|
||||
"directions_boat",
|
||||
"directions_bus",
|
||||
"directions_car",
|
||||
"directions_railway",
|
||||
"directions_run",
|
||||
"directions_subway",
|
||||
"directions_transit",
|
||||
"directions_walk",
|
||||
"disc_full",
|
||||
"dns",
|
||||
"do_not_disturb",
|
||||
"do_not_disturb_alt",
|
||||
"do_not_disturb_off",
|
||||
"do_not_disturb_on",
|
||||
"dock",
|
||||
"domain",
|
||||
"done",
|
||||
"done_all",
|
||||
"donut_large",
|
||||
"donut_small",
|
||||
"drafts",
|
||||
"drag_handle",
|
||||
"drive_eta",
|
||||
"dvr",
|
||||
"edit",
|
||||
"edit_location",
|
||||
"eject",
|
||||
"email",
|
||||
"enhanced_encryption",
|
||||
"equalizer",
|
||||
"error",
|
||||
"error_outline",
|
||||
"euro_symbol",
|
||||
"ev_station",
|
||||
"event",
|
||||
"event_available",
|
||||
"event_busy",
|
||||
"event_note",
|
||||
"event_seat",
|
||||
"exit_to_app",
|
||||
"expand_less",
|
||||
"expand_more",
|
||||
"explicit",
|
||||
"explore",
|
||||
"exposure",
|
||||
"exposure_neg_1",
|
||||
"exposure_neg_2",
|
||||
"exposure_plus_1",
|
||||
"exposure_plus_2",
|
||||
"exposure_zero",
|
||||
"extension",
|
||||
"face",
|
||||
"fast_forward",
|
||||
"fast_rewind",
|
||||
"favorite",
|
||||
"favorite_border",
|
||||
"featured_play_list",
|
||||
"featured_video",
|
||||
"feedback",
|
||||
"fiber_dvr",
|
||||
"fiber_manual_record",
|
||||
"fiber_new",
|
||||
"fiber_pin",
|
||||
"fiber_smart_record",
|
||||
"file_download",
|
||||
"file_upload",
|
||||
"filter",
|
||||
"filter_1",
|
||||
"filter_2",
|
||||
"filter_3",
|
||||
"filter_4",
|
||||
"filter_5",
|
||||
"filter_6",
|
||||
"filter_7",
|
||||
"filter_8",
|
||||
"filter_9",
|
||||
"filter_9_plus",
|
||||
"filter_b_and_w",
|
||||
"filter_center_focus",
|
||||
"filter_drama",
|
||||
"filter_frames",
|
||||
"filter_hdr",
|
||||
"filter_list",
|
||||
"filter_none",
|
||||
"filter_tilt_shift",
|
||||
"filter_vintage",
|
||||
"find_in_page",
|
||||
"find_replace",
|
||||
"fingerprint",
|
||||
"first_page",
|
||||
"fitness_center",
|
||||
"flag",
|
||||
"flare",
|
||||
"flash_auto",
|
||||
"flash_off",
|
||||
"flash_on",
|
||||
"flight",
|
||||
"flight_land",
|
||||
"flight_takeoff",
|
||||
"flip",
|
||||
"flip_to_back",
|
||||
"flip_to_front",
|
||||
"folder",
|
||||
"folder_open",
|
||||
"folder_shared",
|
||||
"folder_special",
|
||||
"font_download",
|
||||
"format_align_center",
|
||||
"format_align_justify",
|
||||
"format_align_left",
|
||||
"format_align_right",
|
||||
"format_bold",
|
||||
"format_clear",
|
||||
"format_color_fill",
|
||||
"format_color_reset",
|
||||
"format_color_text",
|
||||
"format_indent_decrease",
|
||||
"format_indent_increase",
|
||||
"format_italic",
|
||||
"format_line_spacing",
|
||||
"format_list_bulleted",
|
||||
"format_list_numbered",
|
||||
"format_paint",
|
||||
"format_quote",
|
||||
"format_shapes",
|
||||
"format_size",
|
||||
"format_strikethrough",
|
||||
"format_textdirection_l_to_r",
|
||||
"format_textdirection_r_to_l",
|
||||
"format_underlined",
|
||||
"forum",
|
||||
"forward",
|
||||
"forward_10",
|
||||
"forward_30",
|
||||
"forward_5",
|
||||
"free_breakfast",
|
||||
"fullscreen",
|
||||
"fullscreen_exit",
|
||||
"functions",
|
||||
"g_translate",
|
||||
"gamepad",
|
||||
"games",
|
||||
"gavel",
|
||||
"gesture",
|
||||
"get_app",
|
||||
"gif",
|
||||
"golf_course",
|
||||
"gps_fixed",
|
||||
"gps_not_fixed",
|
||||
"gps_off",
|
||||
"grade",
|
||||
"gradient",
|
||||
"grain",
|
||||
"graphic_eq",
|
||||
"grid_off",
|
||||
"grid_on",
|
||||
"group",
|
||||
"group_add",
|
||||
"group_work",
|
||||
"hd",
|
||||
"hdr_off",
|
||||
"hdr_on",
|
||||
"hdr_strong",
|
||||
"hdr_weak",
|
||||
"headset",
|
||||
"headset_mic",
|
||||
"healing",
|
||||
"hearing",
|
||||
"help",
|
||||
"help_outline",
|
||||
"high_quality",
|
||||
"highlight",
|
||||
"highlight_off",
|
||||
"history",
|
||||
"home",
|
||||
"hot_tub",
|
||||
"hotel",
|
||||
"hourglass_empty",
|
||||
"hourglass_full",
|
||||
"http",
|
||||
"https",
|
||||
"image",
|
||||
"image_aspect_ratio",
|
||||
"import_contacts",
|
||||
"import_export",
|
||||
"important_devices",
|
||||
"inbox",
|
||||
"indeterminate_check_box",
|
||||
"info",
|
||||
"info_outline",
|
||||
"input",
|
||||
"insert_chart",
|
||||
"insert_comment",
|
||||
"insert_drive_file",
|
||||
"insert_emoticon",
|
||||
"insert_invitation",
|
||||
"insert_link",
|
||||
"insert_photo",
|
||||
"invert_colors",
|
||||
"invert_colors_off",
|
||||
"iso",
|
||||
"keyboard",
|
||||
"keyboard_arrow_down",
|
||||
"keyboard_arrow_left",
|
||||
"keyboard_arrow_right",
|
||||
"keyboard_arrow_up",
|
||||
"keyboard_backspace",
|
||||
"keyboard_capslock",
|
||||
"keyboard_hide",
|
||||
"keyboard_return",
|
||||
"keyboard_tab",
|
||||
"keyboard_voice",
|
||||
"kitchen",
|
||||
"label",
|
||||
"label_outline",
|
||||
"landscape",
|
||||
"language",
|
||||
"laptop",
|
||||
"laptop_chromebook",
|
||||
"laptop_mac",
|
||||
"laptop_windows",
|
||||
"last_page",
|
||||
"launch",
|
||||
"layers",
|
||||
"layers_clear",
|
||||
"leak_add",
|
||||
"leak_remove",
|
||||
"lens",
|
||||
"library_add",
|
||||
"library_books",
|
||||
"library_music",
|
||||
"lightbulb_outline",
|
||||
"line_style",
|
||||
"line_weight",
|
||||
"linear_scale",
|
||||
"link",
|
||||
"linked_camera",
|
||||
"list",
|
||||
"live_help",
|
||||
"live_tv",
|
||||
"local_activity",
|
||||
"local_airport",
|
||||
"local_atm",
|
||||
"local_bar",
|
||||
"local_cafe",
|
||||
"local_car_wash",
|
||||
"local_convenience_store",
|
||||
"local_dining",
|
||||
"local_drink",
|
||||
"local_florist",
|
||||
"local_gas_station",
|
||||
"local_grocery_store",
|
||||
"local_hospital",
|
||||
"local_hotel",
|
||||
"local_laundry_service",
|
||||
"local_library",
|
||||
"local_mall",
|
||||
"local_movies",
|
||||
"local_offer",
|
||||
"local_parking",
|
||||
"local_pharmacy",
|
||||
"local_phone",
|
||||
"local_pizza",
|
||||
"local_play",
|
||||
"local_post_office",
|
||||
"local_printshop",
|
||||
"local_see",
|
||||
"local_shipping",
|
||||
"local_taxi",
|
||||
"location_city",
|
||||
"location_disabled",
|
||||
"location_off",
|
||||
"location_on",
|
||||
"location_searching",
|
||||
"lock",
|
||||
"lock_open",
|
||||
"lock_outline",
|
||||
"looks",
|
||||
"looks_3",
|
||||
"looks_4",
|
||||
"looks_5",
|
||||
"looks_6",
|
||||
"looks_one",
|
||||
"looks_two",
|
||||
"loop",
|
||||
"loupe",
|
||||
"low_priority",
|
||||
"loyalty",
|
||||
"mail",
|
||||
"mail_outline",
|
||||
"map",
|
||||
"markunread",
|
||||
"markunread_mailbox",
|
||||
"memory",
|
||||
"menu",
|
||||
"merge_type",
|
||||
"message",
|
||||
"mic",
|
||||
"mic_none",
|
||||
"mic_off",
|
||||
"mms",
|
||||
"mode_comment",
|
||||
"mode_edit",
|
||||
"monetization_on",
|
||||
"money_off",
|
||||
"monochrome_photos",
|
||||
"mood",
|
||||
"mood_bad",
|
||||
"more",
|
||||
"more_horiz",
|
||||
"more_vert",
|
||||
"motorcycle",
|
||||
"mouse",
|
||||
"move_to_inbox",
|
||||
"movie",
|
||||
"movie_creation",
|
||||
"movie_filter",
|
||||
"multiline_chart",
|
||||
"music_note",
|
||||
"music_video",
|
||||
"my_location",
|
||||
"nature",
|
||||
"nature_people",
|
||||
"navigate_before",
|
||||
"navigate_next",
|
||||
"navigation",
|
||||
"near_me",
|
||||
"network_cell",
|
||||
"network_check",
|
||||
"network_locked",
|
||||
"network_wifi",
|
||||
"new_releases",
|
||||
"next_week",
|
||||
"nfc",
|
||||
"no_encryption",
|
||||
"no_sim",
|
||||
"not_interested",
|
||||
"note",
|
||||
"note_add",
|
||||
"notifications",
|
||||
"notifications_active",
|
||||
"notifications_none",
|
||||
"notifications_off",
|
||||
"notifications_paused",
|
||||
"offline_pin",
|
||||
"ondemand_video",
|
||||
"opacity",
|
||||
"open_in_browser",
|
||||
"open_in_new",
|
||||
"open_with",
|
||||
"pages",
|
||||
"pageview",
|
||||
"palette",
|
||||
"pan_tool",
|
||||
"panorama",
|
||||
"panorama_fish_eye",
|
||||
"panorama_horizontal",
|
||||
"panorama_vertical",
|
||||
"panorama_wide_angle",
|
||||
"party_mode",
|
||||
"pause",
|
||||
"pause_circle_filled",
|
||||
"pause_circle_outline",
|
||||
"payment",
|
||||
"people",
|
||||
"people_outline",
|
||||
"perm_camera_mic",
|
||||
"perm_contact_calendar",
|
||||
"perm_data_setting",
|
||||
"perm_device_information",
|
||||
"perm_identity",
|
||||
"perm_media",
|
||||
"perm_phone_msg",
|
||||
"perm_scan_wifi",
|
||||
"person",
|
||||
"person_add",
|
||||
"person_outline",
|
||||
"person_pin",
|
||||
"person_pin_circle",
|
||||
"personal_video",
|
||||
"pets",
|
||||
"phone",
|
||||
"phone_android",
|
||||
"phone_bluetooth_speaker",
|
||||
"phone_forwarded",
|
||||
"phone_in_talk",
|
||||
"phone_iphone",
|
||||
"phone_locked",
|
||||
"phone_missed",
|
||||
"phone_paused",
|
||||
"phonelink",
|
||||
"phonelink_erase",
|
||||
"phonelink_lock",
|
||||
"phonelink_off",
|
||||
"phonelink_ring",
|
||||
"phonelink_setup",
|
||||
"photo",
|
||||
"photo_album",
|
||||
"photo_camera",
|
||||
"photo_filter",
|
||||
"photo_library",
|
||||
"photo_size_select_actual",
|
||||
"photo_size_select_large",
|
||||
"photo_size_select_small",
|
||||
"picture_as_pdf",
|
||||
"picture_in_picture",
|
||||
"picture_in_picture_alt",
|
||||
"pie_chart",
|
||||
"pie_chart_outlined",
|
||||
"pin_drop",
|
||||
"place",
|
||||
"play_arrow",
|
||||
"play_circle_filled",
|
||||
"play_circle_outline",
|
||||
"play_for_work",
|
||||
"playlist_add",
|
||||
"playlist_add_check",
|
||||
"playlist_play",
|
||||
"plus_one",
|
||||
"poll",
|
||||
"polymer",
|
||||
"pool",
|
||||
"portable_wifi_off",
|
||||
"portrait",
|
||||
"power",
|
||||
"power_input",
|
||||
"power_settings_new",
|
||||
"pregnant_woman",
|
||||
"present_to_all",
|
||||
"print",
|
||||
"priority_high",
|
||||
"public",
|
||||
"publish",
|
||||
"query_builder",
|
||||
"question_answer",
|
||||
"queue",
|
||||
"queue_music",
|
||||
"queue_play_next",
|
||||
"radio",
|
||||
"radio_button_checked",
|
||||
"radio_button_unchecked",
|
||||
"rate_review",
|
||||
"receipt",
|
||||
"recent_actors",
|
||||
"record_voice_over",
|
||||
"redeem",
|
||||
"redo",
|
||||
"refresh",
|
||||
"remove",
|
||||
"remove_circle",
|
||||
"remove_circle_outline",
|
||||
"remove_from_queue",
|
||||
"remove_red_eye",
|
||||
"remove_shopping_cart",
|
||||
"reorder",
|
||||
"repeat",
|
||||
"repeat_one",
|
||||
"replay",
|
||||
"replay_10",
|
||||
"replay_30",
|
||||
"replay_5",
|
||||
"reply",
|
||||
"reply_all",
|
||||
"report",
|
||||
"report_problem",
|
||||
"restaurant",
|
||||
"restaurant_menu",
|
||||
"restore",
|
||||
"restore_page",
|
||||
"ring_volume",
|
||||
"room",
|
||||
"room_service",
|
||||
"rotate_90_degrees_ccw",
|
||||
"rotate_left",
|
||||
"rotate_right",
|
||||
"rounded_corner",
|
||||
"router",
|
||||
"rowing",
|
||||
"rss_feed",
|
||||
"rv_hookup",
|
||||
"satellite",
|
||||
"save",
|
||||
"scanner",
|
||||
"schedule",
|
||||
"school",
|
||||
"screen_lock_landscape",
|
||||
"screen_lock_portrait",
|
||||
"screen_lock_rotation",
|
||||
"screen_rotation",
|
||||
"screen_share",
|
||||
"sd_card",
|
||||
"sd_storage",
|
||||
"search",
|
||||
"security",
|
||||
"select_all",
|
||||
"send",
|
||||
"sentiment_dissatisfied",
|
||||
"sentiment_neutral",
|
||||
"sentiment_satisfied",
|
||||
"sentiment_very_dissatisfied",
|
||||
"sentiment_very_satisfied",
|
||||
"settings",
|
||||
"settings_applications",
|
||||
"settings_backup_restore",
|
||||
"settings_bluetooth",
|
||||
"settings_brightness",
|
||||
"settings_cell",
|
||||
"settings_ethernet",
|
||||
"settings_input_antenna",
|
||||
"settings_input_component",
|
||||
"settings_input_composite",
|
||||
"settings_input_hdmi",
|
||||
"settings_input_svideo",
|
||||
"settings_overscan",
|
||||
"settings_phone",
|
||||
"settings_power",
|
||||
"settings_remote",
|
||||
"settings_system_daydream",
|
||||
"settings_voice",
|
||||
"share",
|
||||
"shop",
|
||||
"shop_two",
|
||||
"shopping_basket",
|
||||
"shopping_cart",
|
||||
"short_text",
|
||||
"show_chart",
|
||||
"shuffle",
|
||||
"signal_cellular_4_bar",
|
||||
"signal_cellular_connected_no_internet_4_bar",
|
||||
"signal_cellular_no_sim",
|
||||
"signal_cellular_null",
|
||||
"signal_cellular_off",
|
||||
"signal_wifi_4_bar",
|
||||
"signal_wifi_4_bar_lock",
|
||||
"signal_wifi_off",
|
||||
"sim_card",
|
||||
"sim_card_alert",
|
||||
"skip_next",
|
||||
"skip_previous",
|
||||
"slideshow",
|
||||
"slow_motion_video",
|
||||
"smartphone",
|
||||
"smoke_free",
|
||||
"smoking_rooms",
|
||||
"sms",
|
||||
"sms_failed",
|
||||
"snooze",
|
||||
"sort",
|
||||
"sort_by_alpha",
|
||||
"spa",
|
||||
"space_bar",
|
||||
"speaker",
|
||||
"speaker_group",
|
||||
"speaker_notes",
|
||||
"speaker_notes_off",
|
||||
"speaker_phone",
|
||||
"spellcheck",
|
||||
"star",
|
||||
"star_border",
|
||||
"star_half",
|
||||
"stars",
|
||||
"stay_current_landscape",
|
||||
"stay_current_portrait",
|
||||
"stay_primary_landscape",
|
||||
"stay_primary_portrait",
|
||||
"stop",
|
||||
"stop_screen_share",
|
||||
"storage",
|
||||
"store",
|
||||
"store_mall_directory",
|
||||
"straighten",
|
||||
"streetview",
|
||||
"strikethrough_s",
|
||||
"style",
|
||||
"subdirectory_arrow_left",
|
||||
"subdirectory_arrow_right",
|
||||
"subject",
|
||||
"subscriptions",
|
||||
"subtitles",
|
||||
"subway",
|
||||
"supervisor_account",
|
||||
"surround_sound",
|
||||
"swap_calls",
|
||||
"swap_horiz",
|
||||
"swap_vert",
|
||||
"swap_vertical_circle",
|
||||
"switch_camera",
|
||||
"switch_video",
|
||||
"sync",
|
||||
"sync_disabled",
|
||||
"sync_problem",
|
||||
"system_update",
|
||||
"system_update_alt",
|
||||
"tab",
|
||||
"tab_unselected",
|
||||
"tablet",
|
||||
"tablet_android",
|
||||
"tablet_mac",
|
||||
"tag_faces",
|
||||
"tap_and_play",
|
||||
"terrain",
|
||||
"text_fields",
|
||||
"text_format",
|
||||
"textsms",
|
||||
"texture",
|
||||
"theaters",
|
||||
"thumb_down",
|
||||
"thumb_up",
|
||||
"thumbs_up_down",
|
||||
"time_to_leave",
|
||||
"timelapse",
|
||||
"timeline",
|
||||
"timer",
|
||||
"timer_10",
|
||||
"timer_3",
|
||||
"timer_off",
|
||||
"title",
|
||||
"toc",
|
||||
"today",
|
||||
"toll",
|
||||
"tonality",
|
||||
"touch_app",
|
||||
"toys",
|
||||
"track_changes",
|
||||
"traffic",
|
||||
"train",
|
||||
"tram",
|
||||
"transfer_within_a_station",
|
||||
"transform",
|
||||
"translate",
|
||||
"trending_down",
|
||||
"trending_flat",
|
||||
"trending_up",
|
||||
"tune",
|
||||
"turned_in",
|
||||
"turned_in_not",
|
||||
"tv",
|
||||
"unarchive",
|
||||
"undo",
|
||||
"unfold_less",
|
||||
"unfold_more",
|
||||
"update",
|
||||
"usb",
|
||||
"verified_user",
|
||||
"vertical_align_bottom",
|
||||
"vertical_align_center",
|
||||
"vertical_align_top",
|
||||
"vibration",
|
||||
"video_call",
|
||||
"video_label",
|
||||
"video_library",
|
||||
"videocam",
|
||||
"videocam_off",
|
||||
"videogame_asset",
|
||||
"view_agenda",
|
||||
"view_array",
|
||||
"view_carousel",
|
||||
"view_column",
|
||||
"view_comfy",
|
||||
"view_compact",
|
||||
"view_day",
|
||||
"view_headline",
|
||||
"view_list",
|
||||
"view_module",
|
||||
"view_quilt",
|
||||
"view_stream",
|
||||
"view_week",
|
||||
"vignette",
|
||||
"visibility",
|
||||
"visibility_off",
|
||||
"voice_chat",
|
||||
"voicemail",
|
||||
"volume_down",
|
||||
"volume_mute",
|
||||
"volume_off",
|
||||
"volume_up",
|
||||
"vpn_key",
|
||||
"vpn_lock",
|
||||
"wallpaper",
|
||||
"warning",
|
||||
"watch",
|
||||
"watch_later",
|
||||
"wb_auto",
|
||||
"wb_cloudy",
|
||||
"wb_incandescent",
|
||||
"wb_iridescent",
|
||||
"wb_sunny",
|
||||
"wc",
|
||||
"web",
|
||||
"web_asset",
|
||||
"weekend",
|
||||
"whatshot",
|
||||
"widgets",
|
||||
"wifi",
|
||||
"wifi_lock",
|
||||
"wifi_tethering",
|
||||
"work",
|
||||
"wrap_text",
|
||||
"youtube_searched_for",
|
||||
"zoom_in",
|
||||
"zoom_out",
|
||||
"zoom_out_map"
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
render(){
|
||||
|
||||
return (
|
||||
<div className="icon-field">
|
||||
{
|
||||
this.icons().map(icon => {
|
||||
return (
|
||||
<div
|
||||
key={icon}
|
||||
className={"icon-field__option "+(this.props.icon == icon ? "icon-field__option--selected" : "")}
|
||||
onClick={e => this.props.onChange(icon)}>
|
||||
<Icon name={icon} className="icon-field__option__icon" />
|
||||
</div>
|
||||
);
|
||||
})
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1,9 +1,9 @@
|
||||
|
||||
import React, { PropTypes } from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
import { Link } from 'react-router'
|
||||
import { createStore, bindActionCreators } from 'redux'
|
||||
|
||||
import Link from '../Link';
|
||||
import * as helpers from '../../helpers'
|
||||
import * as uiActions from '../../services/ui/actions'
|
||||
import * as lastfmActions from '../../services/lastfm/actions'
|
||||
|
||||
@ -57,12 +57,13 @@ class OutputControl extends React.Component{
|
||||
}
|
||||
|
||||
renderOutputs(){
|
||||
var has_outputs = false;
|
||||
|
||||
var clients = [];
|
||||
for (var key in this.props.snapcast_clients){
|
||||
if (this.props.snapcast_clients.hasOwnProperty(key)){
|
||||
var client = this.props.snapcast_clients[key];
|
||||
if (client.connected){
|
||||
if (client.connected || this.props.show_disconnected_clients){
|
||||
clients.push(client);
|
||||
}
|
||||
}
|
||||
@ -70,27 +71,17 @@ class OutputControl extends React.Component{
|
||||
|
||||
var snapcast_clients = null;
|
||||
if (clients.length > 0){
|
||||
has_outputs = true;
|
||||
snapcast_clients = (
|
||||
<div>
|
||||
{
|
||||
clients.map(client => {
|
||||
|
||||
var commands = {};
|
||||
if (this.props.snapcast_client_commands[client.id]){
|
||||
commands = this.props.snapcast_client_commands[client.id];
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={"output-control__item outputs__item--snapcast"+(commands && commands.power ? " output-control__item--has-power-button": '')} key={client.id}>
|
||||
<div className="output-control__item outputs__item--snapcast" key={client.id}>
|
||||
<div className="output-control__item__name">
|
||||
{client.name}
|
||||
</div>
|
||||
<div className="output-control__item__controls">
|
||||
{commands && commands.power ? <SnapcastPowerButton
|
||||
className="output-control__item__power"
|
||||
client={client}
|
||||
onClick={e => this.props.snapcastActions.sendClientCommand(client.id, commands.power)}
|
||||
/> : null}
|
||||
<MuteControl
|
||||
className="output-control__item__mute"
|
||||
noTooltip={true}
|
||||
@ -114,6 +105,7 @@ class OutputControl extends React.Component{
|
||||
|
||||
var local_streaming = null;
|
||||
if (this.props.http_streaming_enabled){
|
||||
has_outputs = true;
|
||||
local_streaming = (
|
||||
<div className="output-control__item outputs__item--icecast">
|
||||
<div className="output-control__item__actions">
|
||||
@ -137,7 +129,39 @@ class OutputControl extends React.Component{
|
||||
);
|
||||
}
|
||||
|
||||
if (!local_streaming && !snapcast_clients){
|
||||
var commands = null;
|
||||
if (this.props.pusher_commands){
|
||||
|
||||
var commands_items = [];
|
||||
for (var key in this.props.pusher_commands){
|
||||
if (this.props.pusher_commands.hasOwnProperty(key)){
|
||||
commands_items.push(this.props.pusher_commands[key]);
|
||||
}
|
||||
}
|
||||
|
||||
if (commands_items.length > 0){
|
||||
has_outputs = true;
|
||||
commands = (
|
||||
<div className="output-control__item output-control__item--commands commands">
|
||||
{
|
||||
commands_items.map(command => {
|
||||
return (
|
||||
<div
|
||||
key={command.id}
|
||||
className="commands__item commands__item--interactive"
|
||||
onClick={e => this.props.pusherActions.sendCommand(command.id)}>
|
||||
<Icon className="commands__item__icon" name={command.icon} />
|
||||
<span className={command.colour+'-background commands__item__background'}></span>
|
||||
</div>
|
||||
);
|
||||
})
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (!has_outputs){
|
||||
return (
|
||||
<div className="output-control__items output-control__items--no-results">
|
||||
<p className="no-results">No outputs</p>
|
||||
@ -146,6 +170,7 @@ class OutputControl extends React.Component{
|
||||
} else {
|
||||
return (
|
||||
<div className="output-control__items">
|
||||
{commands}
|
||||
{local_streaming}
|
||||
{snapcast_clients}
|
||||
</div>
|
||||
@ -188,8 +213,9 @@ const mapStateToProps = (state, ownProps) => {
|
||||
http_streaming_mute: state.core.http_streaming_mute,
|
||||
pusher_connected: state.pusher.connected,
|
||||
snapcast_enabled: (state.pusher.config ? state.pusher.config.snapcast_enabled : null),
|
||||
show_disconnected_clients: (state.ui.snapcast_show_disconnected_clients !== undefined ? state.ui.snapcast_show_disconnected_clients : false),
|
||||
snapcast_clients: state.snapcast.clients,
|
||||
snapcast_client_commands: (state.snapcast.client_commands ? state.snapcast.client_commands : {})
|
||||
pusher_commands: (state.pusher.commands ? state.pusher.commands : {})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -25,6 +25,11 @@ export default class GridItem extends React.Component{
|
||||
|
||||
handleClick(e){
|
||||
if (this.props.onClick && e.target.tagName.toLowerCase() !== 'a'){
|
||||
|
||||
// Scroll to the top
|
||||
helpers.scrollTo();
|
||||
|
||||
// And then trigger the click event
|
||||
this.props.onClick(e);
|
||||
}
|
||||
}
|
||||
@ -57,8 +62,8 @@ export default class GridItem extends React.Component{
|
||||
case 'artist':
|
||||
return (
|
||||
<span>
|
||||
{item.followers ? item.followers.toLocaleString()+' followers' : null}
|
||||
{item.albums_uris ? item.albums_uris.length+' albums' : null}
|
||||
{item.followers !== undefined ? item.followers.toLocaleString()+' followers' : null}
|
||||
{item.albums_uris !== undefined ? item.albums_uris.length+' albums' : null}
|
||||
</span>
|
||||
)
|
||||
break
|
||||
@ -66,7 +71,7 @@ export default class GridItem extends React.Component{
|
||||
case 'album':
|
||||
return (
|
||||
<span>
|
||||
{item.artists ? <ArtistSentence artists={item.artists} /> : null}
|
||||
{item.artists !== undefined ? <ArtistSentence artists={item.artists} /> : null}
|
||||
</span>
|
||||
)
|
||||
break
|
||||
@ -74,8 +79,8 @@ export default class GridItem extends React.Component{
|
||||
default:
|
||||
return (
|
||||
<span>
|
||||
{ item.artists ? <ArtistSentence artists={ item.artists } /> : null }
|
||||
{ item.followers ? item.followers.toLocaleString()+' followers' : null }
|
||||
{ item.artists !== undefined ? <ArtistSentence artists={ item.artists } /> : null }
|
||||
{ item.followers !== undefined ? item.followers.toLocaleString()+' followers' : null }
|
||||
</span>
|
||||
)
|
||||
}
|
||||
@ -93,16 +98,20 @@ export default class GridItem extends React.Component{
|
||||
}
|
||||
var images = null
|
||||
if (this.props.item.images){
|
||||
images = this.props.item.images
|
||||
if (Array.isArray(this.props.item.images)){
|
||||
images = this.props.item.images[0];
|
||||
} else {
|
||||
images = this.props.item.images;
|
||||
}
|
||||
} else if (this.props.item.icons){
|
||||
images = this.props.item.icons
|
||||
images = this.props.item.icons;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={"grid-item "+this.props.type+"-grid-item"} onClick={e => this.handleClick(e)} onContextMenu={e => this.handleContextMenu(e)}>
|
||||
<Thumbnail size="medium" images={images} />
|
||||
<div className="name">
|
||||
{item.name ? item.name : <span className="dark-grey-text">{item.uri}</span>}
|
||||
{item.name ? item.name : <span className="opaque-text">{item.uri}</span>}
|
||||
</div>
|
||||
<div className="secondary">
|
||||
{this.props.show_source_icon ? <Icon name={helpers.sourceIcon(item.uri)} type="fontawesome" className="source" /> : null}
|
||||
|
||||
@ -2,7 +2,8 @@
|
||||
import React, { PropTypes } from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
import { createStore, bindActionCreators } from 'redux'
|
||||
import { Link, hashHistory } from 'react-router'
|
||||
import { hashHistory } from 'react-router'
|
||||
import Link from './Link'
|
||||
|
||||
import ArtistSentence from './ArtistSentence'
|
||||
import Thumbnail from './Thumbnail'
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
|
||||
import React, { PropTypes } from 'react'
|
||||
|
||||
import Icon from './Icon'
|
||||
import ContextMenuTrigger from './ContextMenuTrigger'
|
||||
import Icon from './Icon';
|
||||
import ContextMenuTrigger from './ContextMenuTrigger';
|
||||
|
||||
export default class Header extends React.Component{
|
||||
|
||||
|
||||
@ -25,9 +25,19 @@ export default class Icon extends React.Component{
|
||||
var src = require('../../assets/icons/'+this.props.name+'.svg');
|
||||
return <img className={className} src={src} onClick={e => this.handleClick(e)} />;
|
||||
|
||||
case 'gif':
|
||||
var src = require('../../assets/icons/'+this.props.name+'.gif');
|
||||
return <img className={className} src={src} onClick={e => this.handleClick(e)} />;
|
||||
|
||||
case 'fontawesome':
|
||||
return <FontAwesome className={className} type="fontawesome" name={this.props.name} onClick={e => this.handleClick(e)} />;
|
||||
|
||||
case 'css':
|
||||
switch (this.props.name){
|
||||
case 'playing':
|
||||
return <i className={className + " icon--playing"}><span></span><span></span><span></span></i>
|
||||
}
|
||||
|
||||
default:
|
||||
return <i className={className} onClick={e => this.handleClick(e)}>{this.props.name}</i>;
|
||||
}
|
||||
|
||||
@ -10,16 +10,17 @@ export default class LazyLoadListener extends React.Component{
|
||||
listening: (this.props.loadKey ? true : false),
|
||||
loadKey: this.props.loadKey
|
||||
}
|
||||
|
||||
|
||||
this.handleScroll = helpers.throttle(this.handleScroll.bind(this), 50);
|
||||
}
|
||||
|
||||
componentWillMount(){
|
||||
window.addEventListener("scroll", this.handleScroll, false);
|
||||
componentDidMount(){
|
||||
this.element = document.getElementById('main');
|
||||
this.element.addEventListener("scroll", this.handleScroll, false);
|
||||
}
|
||||
|
||||
componentWillUnmount(){
|
||||
window.removeEventListener("scroll", this.handleScroll, false);
|
||||
this.element.removeEventListener("scroll", this.handleScroll, false);
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps){
|
||||
@ -35,7 +36,7 @@ export default class LazyLoadListener extends React.Component{
|
||||
if (this.state.listening){
|
||||
|
||||
// At, or nearly at the bottom of the page
|
||||
if ((window.innerHeight + window.scrollY) >= (document.body.offsetHeight - 80)){
|
||||
if (this.element.scrollTop > (this.element.scrollHeight - this.element.offsetHeight - 100)){
|
||||
|
||||
// Immediately stop listening to avoid duplicating pagination requests
|
||||
this.setState(
|
||||
|
||||
@ -1,8 +1,14 @@
|
||||
|
||||
import React from 'react'
|
||||
import { Link } from 'react-router'
|
||||
import React from 'react';
|
||||
import {Link} from 'react-router';
|
||||
|
||||
export default class Link extends React.Component{
|
||||
import * as helpers from '../helpers';
|
||||
|
||||
/**
|
||||
* Extends react-router's Link but provides the ability to hook in to the navigation event
|
||||
* which lets us scroll to the top of our <main> for a more traditional navigation experience
|
||||
**/
|
||||
export default class extends React.Component{
|
||||
|
||||
constructor(props){
|
||||
super(props);
|
||||
@ -10,16 +16,17 @@ export default class Link extends React.Component{
|
||||
|
||||
handleClick(e){
|
||||
if (!this.props.retainScroll){
|
||||
window.scrollTo(0, 0);
|
||||
helpers.scrollTo(this.props.scrollTo, (this.props.scrollTo));
|
||||
}
|
||||
}
|
||||
|
||||
render(){
|
||||
render(){
|
||||
return (
|
||||
<Link
|
||||
onClick={e => this.handleClick(e)}
|
||||
className={this.props.className ? this.props.className : null}
|
||||
to={to}>
|
||||
className={this.props.className ? this.props.className : null}
|
||||
activeClassName={this.props.activeClassName}
|
||||
to={this.props.to}>
|
||||
{this.props.children}
|
||||
</Link>
|
||||
);
|
||||
|
||||
@ -9,6 +9,7 @@ import Dater from './Dater'
|
||||
import URILink from './URILink'
|
||||
import ContextMenuTrigger from './ContextMenuTrigger'
|
||||
import Icon from './Icon'
|
||||
import Thumbnail from './Thumbnail'
|
||||
import Popularity from './Popularity'
|
||||
|
||||
import * as helpers from '../helpers'
|
||||
@ -26,6 +27,7 @@ class List extends React.Component{
|
||||
if (e.target.tagName.toLowerCase() !== 'a'){
|
||||
e.preventDefault();
|
||||
hashHistory.push((this.props.link_prefix ? this.props.link_prefix : '') + encodeURIComponent(uri));
|
||||
helpers.scrollTo();
|
||||
}
|
||||
}
|
||||
|
||||
@ -35,6 +37,7 @@ class List extends React.Component{
|
||||
if (e.target.tagName.toLowerCase() !== 'a'){
|
||||
e.preventDefault();
|
||||
hashHistory.push((this.props.link_prefix ? this.props.link_prefix : '') + encodeURIComponent(uri));
|
||||
helpers.scrollTo();
|
||||
}
|
||||
}
|
||||
|
||||
@ -45,38 +48,23 @@ class List extends React.Component{
|
||||
}
|
||||
}
|
||||
|
||||
renderHeader(){
|
||||
if (!this.props.columns || this.props.noheader) return null
|
||||
|
||||
return (
|
||||
<div className="list__item header cf">
|
||||
<div className="liner">
|
||||
{
|
||||
this.props.columns.map((col, col_index) => {
|
||||
var className = 'col '+col.name.replace('.','_')
|
||||
return <div className={className} key={col_index}>{ col.label ? col.label : col.name }</div>
|
||||
})
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
renderValue(row, key_string){
|
||||
var key = key_string.split('.')
|
||||
var value = row
|
||||
var key = key_string.split('.');
|
||||
var value = row;
|
||||
|
||||
for (var i = 0; i < key.length; i++){
|
||||
if (value[key[i]] === undefined){
|
||||
return <span>-</span>
|
||||
return null
|
||||
} else if (typeof(value[key[i]]) === 'string' && value[key[i]].replace(' ','') == ''){
|
||||
return <span>-</span>
|
||||
return null
|
||||
} else {
|
||||
value = value[key[i]]
|
||||
}
|
||||
}
|
||||
|
||||
if (key_string === 'added_at') return <span><Dater type="ago" data={value} /> ago</span>
|
||||
if (key_string === 'tracks_total' || key_string === 'tracks_uris.length') return <span>{value} tracks</span>
|
||||
if (key_string === 'followers') return <span>{value.toLocaleString()} followers</span>
|
||||
if (key_string === 'added_at') return <span>Added <Dater type="ago" data={value} /> ago</span>
|
||||
if (key_string === 'owner') return <URILink type="user" uri={value.uri}>{value.id}</URILink>
|
||||
if (key_string === 'popularity') return <Popularity full popularity={value} />
|
||||
if (key[0] === 'artists') return <ArtistSentence artists={value} />
|
||||
@ -95,12 +83,25 @@ class List extends React.Component{
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
{ this.renderHeader() }
|
||||
{
|
||||
this.props.rows.map((row, row_index) => {
|
||||
|
||||
var class_name = 'list__item'
|
||||
if (row.type ) class_name += ' '+row.type
|
||||
if (row.type){
|
||||
class_name += ' list__item--'+row.type;
|
||||
}
|
||||
|
||||
if (this.props.middle_column){
|
||||
class_name += " list__item--has-middle-column";
|
||||
}
|
||||
|
||||
if (this.props.thumbnail){
|
||||
class_name += " list__item--has-thumbnail";
|
||||
}
|
||||
|
||||
if (this.props.details){
|
||||
class_name += " list__item--has-details";
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
@ -108,17 +109,60 @@ class List extends React.Component{
|
||||
key={row_index}
|
||||
onClick={e => this.handleClick(e, row.uri)}
|
||||
onContextMenu={e => this.handleContextMenu(e,row)}>
|
||||
{
|
||||
this.props.columns.map((col, col_index) => {
|
||||
var className = 'col '+col.name.replace('.','_')
|
||||
return (
|
||||
<div className={className} key={col_index}>
|
||||
{ this.renderValue(row, col.name) }
|
||||
</div>
|
||||
)
|
||||
})
|
||||
}
|
||||
{this.props.nocontext ? null : <ContextMenuTrigger onTrigger={e => this.handleContextMenu(e, row)} />}
|
||||
|
||||
<div className="list__item__column list__item__column--right">
|
||||
{
|
||||
(this.props.right_column ? this.props.right_column.map((item, index) => {
|
||||
return (
|
||||
<span className={'list__item__column__item list__item__column__item--'+item.replace('.','_')} key={index}>
|
||||
{this.renderValue(row, item)}
|
||||
</span>
|
||||
)
|
||||
}) : null)
|
||||
}
|
||||
|
||||
{this.props.nocontext ? null : <ContextMenuTrigger className="list__item__column__item list__item__column__item--context-menu-trigger subtle" onTrigger={e => this.handleContextMenu(e, row)} />}
|
||||
|
||||
</div>
|
||||
|
||||
<div className="list__item__column list__item__column--name">
|
||||
|
||||
{this.props.thumbnail ? <Thumbnail className="list__item__column__item list__item__column__item--thumbnail" images={(row.images ? row.images : null)} size="small" /> : null}
|
||||
|
||||
<div className="list__item__column__item list__item__column__item--name">
|
||||
{this.renderValue(row, 'name')}
|
||||
</div>
|
||||
|
||||
{this.props.details ?<ul className="list__item__column__item list__item__column__item--details details">
|
||||
{
|
||||
this.props.details.map((item, index) => {
|
||||
var value = this.renderValue(row, item);
|
||||
|
||||
if (!value){
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<li className={'details__item details__item--'+item.replace('.','_')} key={index}>
|
||||
{value}
|
||||
</li>
|
||||
)
|
||||
})
|
||||
}
|
||||
</ul> : null}
|
||||
</div>
|
||||
|
||||
{this.props.middle_column ? <div className="list__item__column list__item__column--middle">
|
||||
{
|
||||
(this.props.middle_column ? this.props.middle_column.map((item, index) => {
|
||||
return (
|
||||
<span className={'list__item__column__item list__item__column__item--'+item.replace('.','_')} key={index}>
|
||||
{this.renderValue(row, item)}
|
||||
</span>
|
||||
)
|
||||
}) : null)
|
||||
}
|
||||
</div> : null}
|
||||
</div>
|
||||
)
|
||||
})
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
|
||||
import React, { PropTypes } from 'react';
|
||||
import { Link, hashHistory } from 'react-router';
|
||||
import { hashHistory } from 'react-router'
|
||||
import Link from './Link';
|
||||
import Icon from './Icon';
|
||||
|
||||
export default class Notifications extends React.Component{
|
||||
@ -29,10 +30,6 @@ export default class Notifications extends React.Component{
|
||||
this.props.geniusActions.importAuthorization(configuration.genius.authorization, configuration.genius.me);
|
||||
}
|
||||
|
||||
if (configuration.snapcast_client_commands){
|
||||
this.props.snapcastActions.clientCommandsUpdated(configuration.snapcast_client_commands);
|
||||
}
|
||||
|
||||
this.props.uiActions.removeNotification(notification_key, true);
|
||||
this.props.uiActions.createNotification({type: 'info', content: 'Import successful'});
|
||||
}
|
||||
@ -72,7 +69,6 @@ export default class Notifications extends React.Component{
|
||||
{notification.configuration.spotify ? <li>Spotify</li> : null}
|
||||
{notification.configuration.lastfm ? <li>LastFM</li> : null}
|
||||
{notification.configuration.genius ? <li>Genius</li> : null}
|
||||
{notification.configuration.snapcast_client_commands ? <li>Snapcast</li> : null}
|
||||
</ul>
|
||||
<p>Do you want to import this?</p>
|
||||
</div>
|
||||
@ -86,7 +82,7 @@ export default class Notifications extends React.Component{
|
||||
<div className={"notification notification--"+notification.type+(notification.closing ? ' closing' : '')} key={notification.key} data-key={notification.key} data-duration={notification.duration}>
|
||||
<Icon name="close" className="close-button" onClick={ e => this.props.uiActions.removeNotification(notification.key, true) } />
|
||||
{notification.title ? <h4>{notification.title}</h4> : null}
|
||||
{notification.content ? <p className="content" dangerouslySetInnerHTML={{__html: notification.content}}></p> :null}
|
||||
{notification.content ? <p className="content" dangerouslySetInnerHTML={{__html: notification.content}}></p> : null}
|
||||
{notification.description ? <p className="description" dangerouslySetInnerHTML={{__html: notification.description}}></p> : null }
|
||||
</div>
|
||||
)
|
||||
|
||||
@ -6,220 +6,68 @@ export default class Parallax extends React.Component{
|
||||
constructor(props){
|
||||
super(props);
|
||||
|
||||
this._loading = false
|
||||
this._loaded = false
|
||||
|
||||
this.state = {
|
||||
scrollTop: 0,
|
||||
windowWidth: 0,
|
||||
windowHeight: 0,
|
||||
canvas: {
|
||||
width: 0,
|
||||
height: 0
|
||||
},
|
||||
image: {},
|
||||
loading: false,
|
||||
url: false
|
||||
}
|
||||
|
||||
if (!this.props.disabled){
|
||||
|
||||
// we need to manually bind this as eventListener doesn't work with anonymous functions
|
||||
this.handleResize = this.handleResize.bind(this);
|
||||
this.handleScroll = this.handleScroll.bind(this);
|
||||
loaded: false,
|
||||
url: null
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount(){
|
||||
if (!this.props.disabled){
|
||||
window.addEventListener("resize", this.handleResize);
|
||||
window.addEventListener("scroll", this.handleScroll);
|
||||
componentWillMount(){
|
||||
if (this.props.image){
|
||||
this.loadImage(this.props.image);
|
||||
}
|
||||
|
||||
this._mounted = true;
|
||||
|
||||
if (this.props.image){
|
||||
this._loading = true;
|
||||
this.setState({url: this.props.image, image: false, loading: true});
|
||||
this.loadImage(this.props.image)
|
||||
.then(
|
||||
response => {
|
||||
if (this._mounted){
|
||||
this._loading = false;
|
||||
this._loaded = true;
|
||||
this.setState({url: this.props.image, image: response, loading: false});
|
||||
this.updateCanvas(response);
|
||||
}
|
||||
}
|
||||
)
|
||||
} else {
|
||||
this._loaded = true;
|
||||
this.state.image = false;
|
||||
this.updateCanvas();
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount(){
|
||||
this._mounted = false;
|
||||
|
||||
if (!this.props.disabled){
|
||||
window.removeEventListener("resize", this.handleResize);
|
||||
window.removeEventListener("scroll", this.handleScroll);
|
||||
}
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps){
|
||||
if ((!this.state.url || nextProps.image != this.state.url ) && !this._loading && nextProps.image){
|
||||
this._loading = true;
|
||||
this.setState({ url: nextProps.image, image: false, loading: true });
|
||||
this.loadImage(nextProps.image)
|
||||
.then(
|
||||
response => {
|
||||
if (this._mounted){
|
||||
this._loading = false;
|
||||
this._loaded = true;
|
||||
this.setState({ url: nextProps.image, image: response, loading: false });
|
||||
this.updateCanvas(response);
|
||||
}
|
||||
}
|
||||
)
|
||||
if (nextProps.image != this.state.url){
|
||||
this.loadImage(nextProps.image);
|
||||
}
|
||||
}
|
||||
|
||||
handleResize(e){
|
||||
this.updateCanvas(this.state.image);
|
||||
}
|
||||
|
||||
handleScroll(e){
|
||||
// this DOES work, but is in no way high-performing and only on Firefox
|
||||
if (this._loaded){
|
||||
this.setState(
|
||||
{scrollTop: window.scrollY},
|
||||
this.updateCanvas(this.state.image)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
loadImage(url){
|
||||
return new Promise((resolve, reject) => {
|
||||
|
||||
// Reset our image to nothing
|
||||
// This is needed when navigating to new view, but not unmounting the current components
|
||||
// ie one artist to another
|
||||
this.setState({
|
||||
loaded: false,
|
||||
url: null
|
||||
});
|
||||
|
||||
if (url && url !== ""){
|
||||
var self = this;
|
||||
var imageObject = new Image();
|
||||
imageObject.src = url;
|
||||
|
||||
// This seems to prevent Dirble images from loading.
|
||||
// Other domains don't seem to mind any more, so perhaps we can remove for good.
|
||||
// TODO: Re-test the nature of crossorigin images
|
||||
// imageObject.crossOrigin = 'anonymous';
|
||||
|
||||
imageObject.onload = function(){
|
||||
var image = {
|
||||
width: imageObject.naturalWidth,
|
||||
height: imageObject.naturalHeight,
|
||||
original_width: imageObject.naturalWidth,
|
||||
original_height: imageObject.naturalHeight,
|
||||
object: imageObject
|
||||
}
|
||||
resolve(image);
|
||||
self.setState({
|
||||
loaded: true,
|
||||
url: url
|
||||
});
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
updateCanvas(image = false){
|
||||
var canvasWidth = $('.parallax').outerWidth();
|
||||
var canvasHeight = $('.parallax').outerHeight();
|
||||
if (this.state.canvas.width != canvasWidth || this.state.canvas.height != canvasHeight){
|
||||
this.setState({
|
||||
canvas: {
|
||||
width: canvasWidth,
|
||||
height: canvasHeight
|
||||
}
|
||||
})
|
||||
}
|
||||
this.renderCanvas(image);
|
||||
}
|
||||
|
||||
renderCanvas(image = false){
|
||||
let self = this;
|
||||
var canvasDOM = document.getElementById('parallax-canvas')
|
||||
var context = canvasDOM.getContext('2d')
|
||||
|
||||
// Fill the background with mid-grey
|
||||
context.beginPath();
|
||||
context.rect(0, 0, this.state.canvas.width, this.state.canvas.height);
|
||||
|
||||
switch (this.props.theme){
|
||||
case 'light':
|
||||
context.fillStyle = "#EDEDED";
|
||||
break;
|
||||
|
||||
case 'dark':
|
||||
default:
|
||||
context.fillStyle = "#121212";
|
||||
}
|
||||
context.fill();
|
||||
|
||||
if (image && !self.state.loading){
|
||||
|
||||
// zoom image to fill canvas, widthwise
|
||||
if (image.width < this.state.canvas.width || image.width > this.state.canvas.width){
|
||||
var scale = this.state.canvas.width / image.width;
|
||||
image.width = image.width * scale;
|
||||
image.height = image.height * scale;
|
||||
}
|
||||
|
||||
// now check for fill heightwise, and zoom in if necessary
|
||||
if (image.height < this.state.canvas.height){
|
||||
var scale = this.state.canvas.height / image.height;
|
||||
image.width = image.width * scale;
|
||||
image.height = image.height * scale;
|
||||
}
|
||||
|
||||
// figure out where we want the image to be, based on scroll position
|
||||
var percent = Math.round(self.state.scrollTop / this.state.canvas.height * 100 );
|
||||
var position = Math.round((this.state.canvas.height / 2) * (percent/100) ) - 100;
|
||||
|
||||
image.x = (this.state.canvas.width / 2 ) - (image.width / 2 );
|
||||
image.y = ((this.state.canvas.height / 2 ) - (image.height / 2 ) ) + ((percent / 100 ) * 100);
|
||||
|
||||
// Actually draw the image on the canvas
|
||||
context.drawImage(image.object, image.x, image.y, image.width, image.height);
|
||||
|
||||
// now update our component
|
||||
self.setState({ image: image });
|
||||
}
|
||||
|
||||
// Construct a gradient overlay
|
||||
context.rect(0, 0, this.state.canvas.width, this.state.canvas.height);
|
||||
let gradient = context.createLinearGradient(0, 0, 0, this.state.canvas.height);
|
||||
|
||||
switch (this.props.theme){
|
||||
case 'light':
|
||||
gradient.addColorStop(0, 'rgba(255,255,255,0.4)');
|
||||
gradient.addColorStop(0.9, 'rgba(255,255,255,1)');
|
||||
break;
|
||||
|
||||
case 'dark':
|
||||
default:
|
||||
gradient.addColorStop(0, 'rgba(24,24,24,0)');
|
||||
gradient.addColorStop(0.9, 'rgba(24,24,24,1)');
|
||||
}
|
||||
|
||||
context.fillStyle = gradient;
|
||||
|
||||
// And now drop it into place
|
||||
context.fill();
|
||||
}
|
||||
|
||||
render(){
|
||||
return (
|
||||
<div className={this.props.blur && !this.props.disabled ? "parallax blur" : "parallax"}>
|
||||
var class_name = "parallax preserve-3d";
|
||||
if (this.props.blur){
|
||||
class_name += " parallax--blur";
|
||||
}
|
||||
if (this.state.loaded){
|
||||
class_name += " parallax--loaded";
|
||||
}
|
||||
|
||||
<canvas
|
||||
id="parallax-canvas"
|
||||
className={!this.state.loading ? 'loaded' : null}
|
||||
width={this.state.canvas.width}
|
||||
height={this.state.canvas.height}>
|
||||
</canvas>
|
||||
var style = {};
|
||||
if (this.state.loaded && this.state.url){
|
||||
style = {backgroundImage: 'url('+this.state.url+')'};
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={class_name}>
|
||||
<div className="parallax__inner preserve-3d">
|
||||
<div className="parallax__image" style={style}></div>
|
||||
<div className="parallax__overlay"></div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
|
||||
import React, { PropTypes } from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
import { Link } from 'react-router'
|
||||
import Link from './Link'
|
||||
import { bindActionCreators } from 'redux'
|
||||
|
||||
import ProgressSlider from './Fields/ProgressSlider'
|
||||
@ -101,7 +101,7 @@ class PlaybackControls extends React.Component{
|
||||
renderConsumeButton(){
|
||||
var button = <a className="control tooltip" onClick={() => this.props.mopidyActions.setConsume(true)}><Icon name="restaurant" type="material" /><span className="tooltip__content">Consume</span></a>
|
||||
if (this.props.consume){
|
||||
button = <a className="control active tooltip" onClick={() => this.props.mopidyActions.setConsume(false)}><Icon name="restaurant" type="material" /><span className="tooltip__content">Consume</span></a>
|
||||
button = <a className="control control--active tooltip" onClick={() => this.props.mopidyActions.setConsume(false)}><Icon name="restaurant" type="material" /><span className="tooltip__content">Consume</span></a>
|
||||
}
|
||||
return button;
|
||||
}
|
||||
@ -109,7 +109,7 @@ class PlaybackControls extends React.Component{
|
||||
renderRandomButton(){
|
||||
var button = <a className="control tooltip" onClick={() => this.props.mopidyActions.setRandom(true)}><Icon name="shuffle" type="material" /><span className="tooltip__content">Shuffle</span></a>
|
||||
if (this.props.random){
|
||||
button = <a className="control active tooltip" onClick={() => this.props.mopidyActions.setRandom(false)}><Icon name="shuffle" type="material" /><span className="tooltip__content">Shuffle</span></a>
|
||||
button = <a className="control control--active tooltip" onClick={() => this.props.mopidyActions.setRandom(false)}><Icon name="shuffle" type="material" /><span className="tooltip__content">Shuffle</span></a>
|
||||
}
|
||||
return button;
|
||||
}
|
||||
@ -117,7 +117,7 @@ class PlaybackControls extends React.Component{
|
||||
renderRepeatButton(){
|
||||
var button = <a className="control tooltip" onClick={() => this.props.mopidyActions.setRepeat(true)}><Icon name="repeat" /><span className="tooltip__content">Repeat</span></a>
|
||||
if (this.props.repeat){
|
||||
button = <a className="control active tooltip" onClick={() => this.props.mopidyActions.setRepeat(false)}><Icon name="repeat" /><span className="tooltip__content">Repeat</span></a>
|
||||
button = <a className="control control--active tooltip" onClick={() => this.props.mopidyActions.setRepeat(false)}><Icon name="repeat" /><span className="tooltip__content">Repeat</span></a>
|
||||
}
|
||||
return button;
|
||||
}
|
||||
@ -129,7 +129,7 @@ class PlaybackControls extends React.Component{
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={(this.state.expanded ? "expanded playback-controls" : "playback-controls")}>
|
||||
<div className={(this.state.expanded ? "playback-controls--expanded playback-controls" : "playback-controls")}>
|
||||
|
||||
{this.props.next_track && this.props.next_track.images ? <Thumbnail className="hide" size="large" images={this.props.next_track.images} /> : null}
|
||||
|
||||
|
||||
@ -20,7 +20,8 @@ class PlaylistGrid extends React.Component{
|
||||
e: e,
|
||||
context: 'playlist',
|
||||
uris: [item.uri],
|
||||
items: [item]
|
||||
items: [item],
|
||||
tracklist_uri: item.uri
|
||||
}
|
||||
this.props.uiActions.showContextMenu(data)
|
||||
}
|
||||
|
||||
@ -26,7 +26,7 @@ class PusherConnectionList extends React.Component{
|
||||
|
||||
render(){
|
||||
if (!this.props.connected){
|
||||
return <div className="pusher-connection-list grey-text">Not connected</div>;
|
||||
return <div className="pusher-connection-list mid_grey-text">Not connected</div>;
|
||||
}
|
||||
|
||||
var connections = [];
|
||||
@ -37,7 +37,7 @@ class PusherConnectionList extends React.Component{
|
||||
}
|
||||
|
||||
if (connections.length <= 0){
|
||||
return <div className="pusher-connection-list grey-text">No connections</div>;
|
||||
return <div className="pusher-connection-list mid_grey-text">No connections</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
@ -51,10 +51,10 @@ class PusherConnectionList extends React.Component{
|
||||
|
||||
return (
|
||||
<div className={is_me ? 'connection cf me':'connection cf'} key={connection.connection_id}>
|
||||
<div className="col w30">{ connection.username } {is_me ? <span>(you)</span> : null }</div>
|
||||
<div className="col w70">
|
||||
<div className="col col--w30">{ connection.username } {is_me ? <span>(you)</span> : null }</div>
|
||||
<div className="col col--w70">
|
||||
{connection.ip}
|
||||
<span className="grey-text"> ({connection.connection_id})</span>
|
||||
<span className="mid_grey-text"> ({connection.connection_id})</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@ -13,21 +13,27 @@ export default class RelatedArtists extends React.Component{
|
||||
if (!this.props.artists ) return null
|
||||
|
||||
return (
|
||||
<div className="list related-artist-list">
|
||||
<div className="related-artists">
|
||||
{
|
||||
this.props.artists.map((artist, index) => {
|
||||
|
||||
var images = artist.images;
|
||||
if (Array.isArray(images)){
|
||||
images = images[0];
|
||||
}
|
||||
|
||||
if (artist.uri){
|
||||
return (
|
||||
<URILink type="artist" uri={artist.uri} key={artist.uri} className="artist">
|
||||
<Thumbnail circle={true} size="small" images={artist.images} />
|
||||
<span className="name">{ artist.name }</span>
|
||||
<URILink type="artist" uri={artist.uri} key={artist.uri} className="related-artists__item related-artists__item--link">
|
||||
<Thumbnail className="related-artists__item__thumbnail" circle={true} size="small" images={images} />
|
||||
<span className="related-artists__item__name">{ artist.name }</span>
|
||||
</URILink>
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
<span key={artist.uri} className="artist">
|
||||
<Thumbnail circle={true} size="small" images={artist.images} />
|
||||
<span className="name">{ artist.name }</span>
|
||||
<span key={artist.uri} className="related-artists__item">
|
||||
<Thumbnail className="related-artists__item__thumbnail" circle={true} size="small" images={images} />
|
||||
<span className="related-artists__item__name">{ artist.name }</span>
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
|
||||
import React, { PropTypes } from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
import { Link } from 'react-router'
|
||||
import Link from './Link'
|
||||
import { bindActionCreators } from 'redux'
|
||||
|
||||
import Thumbnail from './Thumbnail'
|
||||
@ -32,13 +32,13 @@ class Services extends React.Component{
|
||||
}
|
||||
|
||||
componentDidMount(){
|
||||
if (this.props.spotify.me && this.props.core.users[this.props.spotify.me.id] === undefined){
|
||||
if ((this.props.spotify.enabled || this.props.spotify.authorization) && (!this.props.spotify.me || this.props.core.users[this.props.spotify.me.id] === undefined)){
|
||||
this.props.spotifyActions.getMe();
|
||||
}
|
||||
if (this.props.lastfm.authorization && this.props.core.users["lastfm:user:"+this.props.lastfm.authorization.name] === undefined){
|
||||
this.props.lastfmActions.getMe();
|
||||
}
|
||||
if (this.props.genius.me && this.props.core.users["genius:user:"+this.props.genius.me.id] === undefined){
|
||||
if (this.props.genius.authorization && (!this.props.genius.me || this.props.core.users["genius:user:"+this.props.genius.me.id] === undefined)){
|
||||
this.props.geniusActions.getMe();
|
||||
}
|
||||
}
|
||||
@ -76,7 +76,7 @@ class Services extends React.Component{
|
||||
<Thumbnail circle={true} size="small" images={user_object.images} />
|
||||
<span className="user-name">
|
||||
{user_object.name ? user_object.name : user_object.id}
|
||||
{!this.props.spotify.authorization ? <span className="grey-text"> (Limited access)</span> : null}
|
||||
{!this.props.spotify.authorization ? <span className="mid_grey-text"> (Limited access)</span> : null}
|
||||
</span>
|
||||
</URILink>
|
||||
)
|
||||
@ -295,51 +295,51 @@ class Services extends React.Component{
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="menu">
|
||||
<div className="menu" id="services-menu">
|
||||
<div className="menu__inner">
|
||||
<Link className={"menu-item"+(this.props.active == 'spotify' ? ' menu-item--active' : '')} to={this.props.active == 'spotify' ? global.baseURL+'settings' : global.baseURL+'settings/service/spotify'}>
|
||||
<Link className={"menu-item"+(this.props.active == 'spotify' ? ' menu-item--active' : '')} to={this.props.active == 'spotify' ? global.baseURL+'settings' : global.baseURL+'settings/service/spotify'} scrollTo="services-menu">
|
||||
<div className="menu-item__inner">
|
||||
{spotify_icon}
|
||||
<div className="menu-item__title">
|
||||
Spotify
|
||||
</div>
|
||||
{this.props.spotify.authorization ? <span className="status green-text">Authorized</span> : <span className="status grey-text">Read-only</span>}
|
||||
{this.props.spotify.authorization ? <span className="status green-text">Authorized</span> : <span className="status mid_grey-text">Read-only</span>}
|
||||
</div>
|
||||
</Link>
|
||||
<Link className={"menu-item"+(this.props.active == 'lastfm' ? ' menu-item--active' : '')} to={this.props.active == 'lastfm' ? global.baseURL+'settings' : global.baseURL+'settings/service/lastfm'}>
|
||||
<Link className={"menu-item"+(this.props.active == 'lastfm' ? ' menu-item--active' : '')} to={this.props.active == 'lastfm' ? global.baseURL+'settings' : global.baseURL+'settings/service/lastfm'} scrollTo="services-menu">
|
||||
<div className="menu-item__inner">
|
||||
{lastfm_icon}
|
||||
<div className="menu-item__title">
|
||||
LastFM
|
||||
</div>
|
||||
{this.props.lastfm.authorization ? <span className="status green-text">Authorized</span> : <span className="status grey-text">Read-only</span>}
|
||||
{this.props.lastfm.authorization ? <span className="status green-text">Authorized</span> : <span className="status mid_grey-text">Read-only</span>}
|
||||
</div>
|
||||
</Link>
|
||||
<Link className={"menu-item"+(this.props.active == 'genius' ? ' menu-item--active' : '')} to={this.props.active == 'genius' ? global.baseURL+'settings' : global.baseURL+'settings/service/genius'}>
|
||||
<Link className={"menu-item"+(this.props.active == 'genius' ? ' menu-item--active' : '')} to={this.props.active == 'genius' ? global.baseURL+'settings' : global.baseURL+'settings/service/genius'} scrollTo="services-menu">
|
||||
<div className="menu-item__inner">
|
||||
{genius_icon}
|
||||
<div className="menu-item__title">
|
||||
Genius
|
||||
</div>
|
||||
{this.props.genius.authorization ? <span className="status green-text">Authorized</span> : <span className="status grey-text">Unauthorized</span>}
|
||||
{this.props.genius.authorization ? <span className="status green-text">Authorized</span> : <span className="status mid_grey-text">Unauthorized</span>}
|
||||
</div>
|
||||
</Link>
|
||||
<Link className={"menu-item"+(this.props.active == 'snapcast' ? ' menu-item--active' : '')} to={this.props.active == 'snapcast' ? global.baseURL+'settings' : global.baseURL+'settings/service/snapcast'}>
|
||||
<Link className={"menu-item"+(this.props.active == 'snapcast' ? ' menu-item--active' : '')} to={this.props.active == 'snapcast' ? global.baseURL+'settings' : global.baseURL+'settings/service/snapcast'} scrollTo="services-menu">
|
||||
<div className="menu-item__inner">
|
||||
<Icon className="menu-item__icon" name="devices" />
|
||||
<div className="menu-item__title">
|
||||
Snapcast
|
||||
</div>
|
||||
{this.props.pusher.config.snapcast_enabled ? <span className="status green-text">Enabled</span> : <span className="status grey-text">Disabled</span>}
|
||||
{this.props.pusher.config.snapcast_enabled ? <span className="status green-text">Enabled</span> : <span className="status mid_grey-text">Disabled</span>}
|
||||
</div>
|
||||
</Link>
|
||||
<Link className={"menu-item"+(this.props.active == 'icecast' ? ' menu-item--active' : '')} to={this.props.active == 'icecast' ? global.baseURL+'settings' : global.baseURL+'settings/service/icecast'}>
|
||||
<Link className={"menu-item"+(this.props.active == 'icecast' ? ' menu-item--active' : '')} to={this.props.active == 'icecast' ? global.baseURL+'settings' : global.baseURL+'settings/service/icecast'} scrollTo="services-menu">
|
||||
<div className="menu-item__inner">
|
||||
<Icon className="menu-item__icon" name="wifi_tethering" />
|
||||
<div className="menu-item__title">
|
||||
Icecast
|
||||
</div>
|
||||
{this.props.core.http_streaming_enabled ? <span className="status green-text">Enabled</span> : <span className="status grey-text">Disabled</span>}
|
||||
{this.props.core.http_streaming_enabled ? <span className="status green-text">Enabled</span> : <span className="status mid_grey-text">Disabled</span>}
|
||||
</div>
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
@ -2,8 +2,9 @@
|
||||
import React, { PropTypes } from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
import { bindActionCreators } from 'redux'
|
||||
import { Link, withRouter } from 'react-router'
|
||||
import { withRouter } from 'react-router'
|
||||
|
||||
import Link from './Link';
|
||||
import Icon from './Icon'
|
||||
import Dropzones from './Fields/Dropzones'
|
||||
import Thumbnail from './Thumbnail'
|
||||
|
||||
@ -2,9 +2,11 @@
|
||||
import React, { PropTypes } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import { Link, hashHistory } from 'react-router';
|
||||
import { hashHistory } from 'react-router'
|
||||
import Link from './Link';
|
||||
|
||||
import VolumeControl from './Fields/VolumeControl';
|
||||
import MuteControl from './Fields/MuteControl';
|
||||
import LatencyControl from './Fields/LatencyControl';
|
||||
import TextField from './Fields/TextField';
|
||||
import DropdownField from './Fields/DropdownField';
|
||||
@ -69,7 +71,7 @@ class Snapcast extends React.Component{
|
||||
|
||||
if (!clients || clients.length <= 0){
|
||||
return (
|
||||
<div className="text grey-text">
|
||||
<div className="text mid_grey-text">
|
||||
No clients
|
||||
</div>
|
||||
);
|
||||
@ -87,12 +89,6 @@ class Snapcast extends React.Component{
|
||||
}
|
||||
|
||||
if (this.state.clients_expanded.includes(client.id)){
|
||||
|
||||
var commands = {};
|
||||
if (this.props.client_commands[client.id]){
|
||||
commands = this.props.client_commands[client.id];
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={class_name+" snapcast__client--expanded"} key={client.id}>
|
||||
<div className="snapcast__client__header" onClick={e => this.toggleClientExpanded(client.id)}>
|
||||
@ -140,12 +136,15 @@ class Snapcast extends React.Component{
|
||||
Volume
|
||||
</div>
|
||||
<div className="input">
|
||||
<MuteControl
|
||||
className="snapcast__client__mute-control"
|
||||
mute={client.mute}
|
||||
onMuteChange={mute => this.props.snapcastActions.setClientMute(client.id, mute)}
|
||||
/>
|
||||
<VolumeControl
|
||||
className="snapcast__client__volume-control"
|
||||
volume={client.volume}
|
||||
mute={client.mute}
|
||||
onVolumeChange={percent => this.props.snapcastActions.setClientVolume(client.id, percent)}
|
||||
onMuteChange={mute => this.props.snapcastActions.setClientMute(client.id, mute)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@ -167,21 +166,6 @@ class Snapcast extends React.Component{
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="field">
|
||||
<div className="name tooltip">
|
||||
Power command
|
||||
<span className="tooltip__content">
|
||||
Ajax request to trigger power
|
||||
</span>
|
||||
</div>
|
||||
<div className="input">
|
||||
<TextField
|
||||
onChange={value => this.props.snapcastActions.setClientCommand(client.id, {power: value})}
|
||||
value={commands.power ? commands.power : ""}
|
||||
placeholder='{"url":"https://myserver.local:8080/sendCommand/power"}'
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@ -274,7 +258,7 @@ class Snapcast extends React.Component{
|
||||
<div className="input">
|
||||
<div className="text">
|
||||
{group.name}
|
||||
<span className="grey-text">({group.id})</span>
|
||||
<span className="mid_grey-text">({group.id})</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -301,12 +285,15 @@ class Snapcast extends React.Component{
|
||||
Volume
|
||||
</div>
|
||||
<div className="input">
|
||||
<MuteControl
|
||||
className="snapcast__group__mute-control"
|
||||
mute={group.muted}
|
||||
onMuteChange={mute => this.props.snapcastActions.setGroupMute(group.id, mute)}
|
||||
/>
|
||||
<VolumeControl
|
||||
className="snapcast__group__volume-control"
|
||||
volume={group_volume}
|
||||
mute={group.muted}
|
||||
onVolumeChange={(percent, old_percent) => this.props.snapcastActions.setGroupVolume(group.id, percent, old_percent)}
|
||||
onMuteChange={mute => this.props.snapcastActions.setGroupMute(group.id, mute)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@ -335,8 +322,7 @@ const mapStateToProps = (state, ownProps) => {
|
||||
show_disconnected_clients: (state.ui.snapcast_show_disconnected_clients !== undefined ? state.ui.snapcast_show_disconnected_clients : false),
|
||||
streams: (state.snapcast.streams ? state.snapcast.streams : null),
|
||||
groups: (state.snapcast.groups ? state.snapcast.groups : null),
|
||||
clients: (state.snapcast.clients ? state.snapcast.clients : null),
|
||||
client_commands: (state.snapcast.client_commands ? state.snapcast.client_commands : {})
|
||||
clients: (state.snapcast.clients ? state.snapcast.clients : null)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
import React, { PropTypes } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { createStore, bindActionCreators } from 'redux';
|
||||
import { Link } from 'react-router';
|
||||
import Link from './Link';
|
||||
|
||||
import Icon from './Icon'
|
||||
|
||||
@ -14,35 +14,8 @@ class Thumbnail extends React.Component{
|
||||
constructor(props){
|
||||
super(props);
|
||||
}
|
||||
/*
|
||||
// TODO: ascertain whether this is improving or hindering performance
|
||||
// The UI appears to work perfectly fine without this
|
||||
shouldComponentUpdate(nextProps, nextState){
|
||||
|
||||
// no images at all, and we already know it
|
||||
if (!nextProps.image && !this.props.image && !nextProps.images && !this.props.images) return false
|
||||
|
||||
// image changed
|
||||
if (!this.props.image && nextProps.image) return true
|
||||
if (this.props.image && nextProps.image) return true
|
||||
if (this.props.image != nextProps.image) return true
|
||||
|
||||
// images array changed
|
||||
if (this.props.images === undefined && nextProps.images ) return true
|
||||
if (this.props.images && nextProps.images === undefined) return true
|
||||
if (this.props.images && !nextProps.images || this.props.images.length != nextProps.images.length ) return true
|
||||
|
||||
// image item changed
|
||||
var size = 'medium'
|
||||
var images = helpers.sizedImages(nextProps.images )
|
||||
if (this.props.size ) size = this.props.size
|
||||
if (this.props.images[size] != images[size]) return true
|
||||
|
||||
return false
|
||||
}
|
||||
*/
|
||||
|
||||
mapImageSizes(props = this.props){
|
||||
mapImageSizes(){
|
||||
|
||||
// Single image
|
||||
if (this.props.image){
|
||||
@ -50,6 +23,12 @@ class Thumbnail extends React.Component{
|
||||
|
||||
// Multiple images
|
||||
} else if (this.props.images){
|
||||
var images = this.props.images;
|
||||
|
||||
// An array of image objects (eg Artists), so just pick the first one
|
||||
if (Array.isArray(images) && images.length > 0){
|
||||
images = images[0];
|
||||
}
|
||||
|
||||
// Default to medium-sized image, but accept size property as override
|
||||
var size = 'medium';
|
||||
@ -57,7 +36,10 @@ class Thumbnail extends React.Component{
|
||||
size = this.props.size;
|
||||
}
|
||||
|
||||
return this.props.images[size];
|
||||
// Return the requested size
|
||||
if (images[size]){
|
||||
return images[size];
|
||||
}
|
||||
}
|
||||
|
||||
// No images
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
|
||||
import React, { PropTypes } from 'react'
|
||||
import { Link } from 'react-router'
|
||||
import Link from './Link'
|
||||
|
||||
import Icon from './Icon'
|
||||
import ArtistSentence from './ArtistSentence'
|
||||
@ -182,51 +182,41 @@ export default class Track extends React.Component{
|
||||
}
|
||||
|
||||
var track = this.props.track;
|
||||
var className = 'list__item track mouse-draggable mouse-selectable mouse-contextable';
|
||||
if (this.props.selected) className += ' selected';
|
||||
if (this.props.can_sort) className += ' can-sort';
|
||||
if (track.type !== undefined) className += ' '+track.type;
|
||||
if (track.playing) className += ' playing';
|
||||
if (this.state.hover) className += ' hover';
|
||||
|
||||
var album = '-';
|
||||
if (track.album){
|
||||
if (track.album.uri){
|
||||
album = <URILink type="album" uri={track.album.uri}>{track.album.name}</URILink>;
|
||||
} else {
|
||||
album = <span>{track.album.name}</span>;
|
||||
}
|
||||
var className = 'list__item list__item--track mouse-draggable mouse-selectable mouse-contextable';
|
||||
var track_details = [];
|
||||
var track_actions = [];
|
||||
|
||||
if (track.artists){
|
||||
track_details.push(
|
||||
<li className="details__item details__item--artists" key="artists">
|
||||
{track.artists ? <ArtistSentence artists={track.artists} /> : '-'}
|
||||
</li>
|
||||
)
|
||||
}
|
||||
|
||||
let track_columns = [];
|
||||
let track_actions = [];
|
||||
if (track.album){
|
||||
|
||||
if (track.album.uri){
|
||||
var album = <URILink type="album" uri={track.album.uri}>{track.album.name}</URILink>;
|
||||
} else {
|
||||
var album = <span>{track.album.name}</span>;
|
||||
}
|
||||
|
||||
track_details.push(
|
||||
<li className="details__item details__item--album" key="album">
|
||||
{album}
|
||||
</li>
|
||||
)
|
||||
}
|
||||
|
||||
if (this.props.context == 'history'){
|
||||
|
||||
track_columns.push(
|
||||
<span className="col name" key="name">
|
||||
{track.name ? track.name : <span className="grey-text">{track.uri}</span>}
|
||||
{track.explicit ? <span className="flag dark">EXPLICIT</span> : null}
|
||||
</span>
|
||||
)
|
||||
track_columns.push(
|
||||
<span className="col artists" key="artists">
|
||||
{track.artists ? <ArtistSentence artists={track.artists} /> : '-'}
|
||||
</span>
|
||||
)
|
||||
track_columns.push(
|
||||
<span className="col album" key="album">
|
||||
{album}
|
||||
</span>
|
||||
)
|
||||
track_columns.push(
|
||||
<span className="col played_at" key="played_at">
|
||||
var track_middle_column = (
|
||||
<div className="list__item__column__item list__item__column__item--played_at">
|
||||
{track.played_at ? <span><Dater type="ago" data={track.played_at} /> ago</span> : '-'}
|
||||
</span>
|
||||
</div>
|
||||
)
|
||||
|
||||
} else if (this.props.context == 'queue'){
|
||||
|
||||
if (track.added_from && track.added_by){
|
||||
var type = (track.added_from ? helpers.uriType(track.added_from) : null);
|
||||
|
||||
@ -247,101 +237,44 @@ export default class Track extends React.Component{
|
||||
var link = <URILink type={type} uri={track.added_from}>{type}</URILink>;
|
||||
}
|
||||
|
||||
var added = <span><span className="by">{track.added_by}</span><span className="grey-text from"><span className="label">from </span>{link}</span></span>
|
||||
var track_middle_column = (
|
||||
<div className="list__item__column__item list__item__column__item--added">
|
||||
<span className="by">{track.added_by} </span>
|
||||
<span className="from">from {link}</span>
|
||||
</div>
|
||||
);
|
||||
|
||||
} else if (track.added_by){
|
||||
var added = <span className="by">{track.added_by}</span>
|
||||
|
||||
} else {
|
||||
var added = <span className="empty">-</span>
|
||||
}
|
||||
|
||||
track_columns.push(
|
||||
<span className="col name" key="name">
|
||||
{track.name ? track.name : <span className="grey-text">{track.uri}</span>}
|
||||
{track.explicit ? <span className="flag dark">EXPLICIT</span> : null}
|
||||
</span>
|
||||
)
|
||||
if (this.props.show_source_icon){
|
||||
track_columns.push(
|
||||
<Icon type="fontawesome" name={helpers.sourceIcon(track.uri)} className="source" key="source" fixedWidth />
|
||||
)
|
||||
}
|
||||
track_columns.push(
|
||||
<span className="col artists" key="artists">
|
||||
{track.artists ? <ArtistSentence artists={track.artists} /> : '-'}
|
||||
</span>
|
||||
)
|
||||
track_columns.push(
|
||||
<span className="col album" key="album">
|
||||
{album}
|
||||
</span>
|
||||
)
|
||||
track_columns.push(
|
||||
<span className="col added" key="added">
|
||||
{added}
|
||||
</span>
|
||||
)
|
||||
track_columns.push(
|
||||
<span className="col duration" key="duration">
|
||||
{track.duration ? <Dater type="length" data={track.duration} /> : null}
|
||||
</span>
|
||||
)
|
||||
|
||||
} else {
|
||||
|
||||
track_columns.push(
|
||||
<span className="col name" key="name">
|
||||
{track.name ? track.name : <span className="grey-text">{track.uri}</span>}
|
||||
{track.explicit ? <span className="flag dark">EXPLICIT</span> : null}
|
||||
</span>
|
||||
)
|
||||
if (this.props.show_source_icon){
|
||||
track_columns.push(
|
||||
<Icon type="fontawesome" name={helpers.sourceIcon(track.uri)} className="source" key="source" fixedWidth />
|
||||
)
|
||||
}
|
||||
track_columns.push(
|
||||
<span className="col artists" key="artists">
|
||||
{track.artists ? <ArtistSentence artists={track.artists} /> : '-'}
|
||||
</span>
|
||||
)
|
||||
track_columns.push(
|
||||
<span className="col album" key="album">
|
||||
{album}
|
||||
</span>
|
||||
)
|
||||
track_columns.push(
|
||||
<span className="col duration" key="duration">
|
||||
{track.duration ? <Dater type="length" data={track.duration} /> : '-'}
|
||||
</span>
|
||||
)
|
||||
if (track.popularity !== undefined){
|
||||
track_columns.push(
|
||||
<span className="col popularity" key="popularity">
|
||||
<Popularity popularity={track.popularity} />
|
||||
</span>
|
||||
)
|
||||
var track_middle_column = (
|
||||
<div className="list__item__column__item list__item__column__item--added">
|
||||
<span className="by">{track.added_by}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
track_actions.push(
|
||||
<ContextMenuTrigger className="subtle" key="context" onTrigger={e => this.props.handleContextMenu(e)} />
|
||||
)
|
||||
|
||||
// If we're touchable, and can sort this tracklist
|
||||
var drag_zone = null;
|
||||
if (helpers.isTouchDevice() && this.props.can_sort){
|
||||
className += " has-touch-drag-zone"
|
||||
className += " list__item--has-drag-zone"
|
||||
|
||||
track_actions.push(
|
||||
drag_zone = (
|
||||
<span
|
||||
className="drag-zone touch-draggable mouse-draggable"
|
||||
className="list__item__column__item list__item__column__item--drag-zone drag-zone touch-draggable mouse-draggable"
|
||||
key="drag-zone">
|
||||
<Icon name="drag_indicator" />
|
||||
</span>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (this.props.selected) className += ' list__item--selected';
|
||||
if (this.props.can_sort) className += ' list__item--can-sort';
|
||||
if (track.type !== undefined) className += ' list__item--'+track.type;
|
||||
if (track.playing) className += ' list__item--playing';
|
||||
if (this.state.hover) className += ' list__item--hover';
|
||||
if (track_middle_column) className += " list__item--has-middle-column";
|
||||
if (track_details.length > 0) className += " list__item--has-details";
|
||||
|
||||
return (
|
||||
<ErrorBoundary>
|
||||
<div
|
||||
@ -355,8 +288,27 @@ export default class Track extends React.Component{
|
||||
onContextMenu={e => this.props.handleContextMenu(e)}
|
||||
onTouchStart={e => this.handleTouchStart(e)}
|
||||
onTouchEnd={e => this.handleTouchEnd(e)}>
|
||||
{track_actions}
|
||||
{track_columns}
|
||||
<div className="list__item__column list__item__column--right">
|
||||
{drag_zone}
|
||||
<span className="list__item__column__item list__item__column__item--duration">
|
||||
{track.duration ? <Dater type="length" data={track.duration} /> : '-'}
|
||||
</span>
|
||||
{this.props.show_source_icon ? <span className="list__item__column__item list__item__column__item--source">
|
||||
<Icon type="fontawesome" name={helpers.sourceIcon(track.uri)} fixedWidth />
|
||||
</span> : null}
|
||||
<ContextMenuTrigger className="list__item__column__item--context-menu-trigger subtle" onTrigger={e => this.props.handleContextMenu(e)} />
|
||||
</div>
|
||||
<div className="list__item__column list__item__column--name">
|
||||
<div className="list__item__column__item--name">
|
||||
{track.name ? track.name : <span className="mid_grey-text">{track.uri}</span>}
|
||||
{track.explicit ? <span className="flag dark">EXPLICIT</span> : null}
|
||||
{track.playing ? <Icon className={"js--"+this.props.play_state} name="playing" type="css"></Icon> : null}
|
||||
</div>
|
||||
{track_details ? <ul className="list__item__column__item--details">
|
||||
{track_details}
|
||||
</ul> : null}
|
||||
</div>
|
||||
{track_middle_column ? <div className="list__item__column list__item__column--middle">{track_middle_column}</div> : null}
|
||||
</div>
|
||||
</ErrorBoundary>
|
||||
);
|
||||
|
||||
@ -134,7 +134,7 @@ class TrackList extends React.Component{
|
||||
let touch = e.touches[0];
|
||||
let over = $(document.elementFromPoint(touch.clientX, touch.clientY));
|
||||
if (!over.is('.track')){
|
||||
over = over.closest('.track');
|
||||
over = over.closest('.list__item--track');
|
||||
}
|
||||
$(document).find('.touch-drag-hover').removeClass('touch-drag-hover');
|
||||
if (over.length > 0){
|
||||
@ -153,12 +153,12 @@ class TrackList extends React.Component{
|
||||
if (this.touch_dragging_tracks_keys){
|
||||
let touch = e.changedTouches[0];
|
||||
let over = $(document.elementFromPoint(touch.clientX, touch.clientY));
|
||||
if (!over.is('.track')){
|
||||
over = over.closest('.track');
|
||||
if (!over.is('.list__item--track')){
|
||||
over = over.closest('.list__item--track');
|
||||
}
|
||||
if (over.length > 0){
|
||||
let siblings = over.parent().children('.track');
|
||||
let dropped_at = siblings.index(over) - 1;
|
||||
let siblings = over.parent().children('.list__item--track');
|
||||
let dropped_at = siblings.index(over);
|
||||
|
||||
if (this.props.reorderTracks !== undefined){
|
||||
this.props.reorderTracks(helpers.arrayOf('index',this.digestTracksKeys()),dropped_at);
|
||||
@ -350,7 +350,7 @@ class TrackList extends React.Component{
|
||||
}
|
||||
|
||||
// Accommodate a single key
|
||||
let singleton = false
|
||||
var singleton = false
|
||||
if (!(keys instanceof Array)){
|
||||
singleton = true
|
||||
keys = [keys]
|
||||
@ -358,20 +358,21 @@ class TrackList extends React.Component{
|
||||
|
||||
// Construct a basic track object, based on our unique track key
|
||||
// This is enough to perform interactions (dragging, selecting, etc)
|
||||
let array = []
|
||||
for (let i = 0; i < keys.length; i++){
|
||||
let key = keys[i].split('@@')
|
||||
var array = [];
|
||||
for (var key of keys){
|
||||
var key_components = key.split('@@')
|
||||
|
||||
if (indexes_only){
|
||||
array.push(key[0])
|
||||
array.push(key_components[0])
|
||||
|
||||
} else {
|
||||
array.push({
|
||||
index: parseInt(key[0]),
|
||||
tlid: parseInt(key[1]),
|
||||
uri: key[2],
|
||||
context: key[3],
|
||||
context_uri: key[4]
|
||||
key: key,
|
||||
index: parseInt(key_components[0]),
|
||||
tlid: parseInt(key_components[1]),
|
||||
uri: key_components[2],
|
||||
context: key_components[3],
|
||||
context_uri: key_components[4]
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -383,64 +384,19 @@ class TrackList extends React.Component{
|
||||
}
|
||||
}
|
||||
|
||||
renderHeader(){
|
||||
if (this.props.noheader ) return null
|
||||
|
||||
switch (this.props.context){
|
||||
|
||||
case 'history':
|
||||
return (
|
||||
<div className="list__item header track">
|
||||
<div className="liner">
|
||||
<span className="col name">Name</span>
|
||||
<span className="col artists">Artists</span>
|
||||
<span className="col album">Album</span>
|
||||
<span className="col played_at">Started playing</span>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
break
|
||||
|
||||
case 'queue':
|
||||
return (
|
||||
<div className="list__item header track">
|
||||
<div className="liner">
|
||||
<span className="col name">Name</span>
|
||||
<span className="col artists">Artists</span>
|
||||
<span className="col album">Album</span>
|
||||
<span className="col added">Added by</span>
|
||||
<span className="col duration">Duration</span>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
break
|
||||
|
||||
default:
|
||||
return (
|
||||
<div className="list__item header track">
|
||||
<div className="liner">
|
||||
<span className="col name">Name</span>
|
||||
<span className="col artists">Artists</span>
|
||||
<span className="col album">Album</span>
|
||||
<span className="col duration">Duration</span>
|
||||
<span className="col popularity"></span>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
render(){
|
||||
if (!this.props.tracks || Object.prototype.toString.call(this.props.tracks) !== '[object Array]' ) return null
|
||||
if (!this.props.tracks || Object.prototype.toString.call(this.props.tracks) !== '[object Array]' ){
|
||||
return null;
|
||||
}
|
||||
|
||||
var className = 'list track-list '+this.props.context
|
||||
var className = 'list list--tracks '+this.props.context
|
||||
if (this.props.className){
|
||||
className += ' '+this.props.className
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
{ this.renderHeader() }
|
||||
{
|
||||
this.props.tracks.map(
|
||||
(track, index) => {
|
||||
@ -455,6 +411,7 @@ class TrackList extends React.Component{
|
||||
context={this.props.context}
|
||||
can_sort={this.props.context == 'queue' || this.props.context == 'editable-playlist'}
|
||||
selected={this.props.selected_tracks.includes(track_key)}
|
||||
play_state={this.props.play_state}
|
||||
dragger={this.props.dragger}
|
||||
handleClick={(e) => this.handleClick(e, track_key)}
|
||||
handleDoubleClick={e => this.handleDoubleClick(e, track_key)}
|
||||
@ -474,15 +431,9 @@ class TrackList extends React.Component{
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Export our component
|
||||
*
|
||||
* We also integrate our global store, using connect()
|
||||
**/
|
||||
|
||||
const mapStateToProps = (state, ownProps) => {
|
||||
return {
|
||||
play_state: state.mopidy.play_state,
|
||||
slim_mode: state.ui.slim_mode,
|
||||
selected_tracks: state.ui.selected_tracks,
|
||||
dragger: state.ui.dragger,
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
|
||||
import React from 'react'
|
||||
import { Link } from 'react-router'
|
||||
import React from 'react';
|
||||
import Link from './Link';
|
||||
|
||||
export default class URILink extends React.Component{
|
||||
|
||||
|
||||
Reference in New Issue
Block a user