From 232f78289698484260ec0a42b0cb71ae6dbb7762 Mon Sep 17 00:00:00 2001 From: Eric Lehmann Date: Mon, 26 Feb 2018 08:16:01 +0100 Subject: [PATCH 01/27] Fixes missing single quotes in path --- src/constants.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/constants.js b/src/constants.js index cafc2188..7e8aca22 100644 --- a/src/constants.js +++ b/src/constants.js @@ -10,7 +10,7 @@ export const DEFAULT_DESCRIPTOR = 'lowlevel.mfcc.mean'; export const PERFORM_QUERY_AT_MOUNT = false; // backend and login urls -const BACKEND_APPLICATION_ROOT = /fse/backend // -> must match with backend's application root +const BACKEND_APPLICATION_ROOT = '/fse/backend' // -> must match with backend's application root export const URLS = { SAVE_SESSION: `${BACKEND_APPLICATION_ROOT}/save/`, LOAD_SESSION: `${BACKEND_APPLICATION_ROOT}/load/`, From d206f1014be09b01d461b9d432a6018bbb2b6ed7 Mon Sep 17 00:00:00 2001 From: Eric Lehmann Date: Mon, 26 Feb 2018 10:04:23 +0100 Subject: [PATCH 02/27] Refactors search function for less ambiguity in repository --- src/vendors/freesound.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/vendors/freesound.js b/src/vendors/freesound.js index 931f5259..febe9e0c 100644 --- a/src/vendors/freesound.js +++ b/src/vendors/freesound.js @@ -87,7 +87,7 @@ const freesound = (function () { return fd; }; - var search = function(options, uri, success, error,wrapper){ + var searchFS = function(options, uri, success, error,wrapper){ if(options.analysis_file){ makeRequest(makeUri(uri), success,error,null, wrapper, 'POST',makeFD(options)); } @@ -250,17 +250,17 @@ const freesound = (function () { textSearch: function(query, options, success, error){ options = options || {}; options.query = query ? query : " "; - return search(options,uris.textSearch,success,error,SoundCollection); + return searchFS(options,uris.textSearch,success,error,SoundCollection); }, contentSearch: function(options, success, error){ if(!(options.target || options.analysis_file)) throw("Missing target or analysis_file"); - search(options,uris.contentSearch,success,error,SoundCollection); + searchFS(options,uris.contentSearch,success,error,SoundCollection); }, combinedSearch:function(options, success, error){ if(!(options.target || options.analysis_file || options.query)) throw("Missing query, target or analysis_file"); - search(options,uris.contentSearch,success,error); + searchFS(options,uris.contentSearch,success,error); }, getSound: function(soundId,success, error){ makeRequest(makeUri(uris.sound, [soundId]), success,error,{}, SoundObject); From 022d6cd70b9cce351f5578c17bbfa43ceebeee31 Mon Sep 17 00:00:00 2001 From: Eric Lehmann Date: Mon, 26 Feb 2018 08:18:05 +0100 Subject: [PATCH 03/27] Introduces new navbar tab, basic version only listing soundIDs with double navbar search logo --- src/components/Sidebar/SidebarContent.jsx | 3 ++ src/components/Sidebar/SidebarNavMenu.jsx | 2 + src/components/Sidebar/SoundListTab.jsx | 15 +++++++ src/components/Sidebar/SoundListTab.scss | 0 src/components/Sounds/SoundList.jsx | 30 ++++++++++++++ src/components/Sounds/SoundList.scss | 0 src/constants.js | 1 + src/containers/Sounds/SoundListContainer.jsx | 41 ++++++++++++++++++++ 8 files changed, 92 insertions(+) create mode 100644 src/components/Sidebar/SoundListTab.jsx create mode 100644 src/components/Sidebar/SoundListTab.scss create mode 100644 src/components/Sounds/SoundList.jsx create mode 100644 src/components/Sounds/SoundList.scss create mode 100644 src/containers/Sounds/SoundListContainer.jsx diff --git a/src/components/Sidebar/SidebarContent.jsx b/src/components/Sidebar/SidebarContent.jsx index 2dfa3df3..cd97c3a3 100644 --- a/src/components/Sidebar/SidebarContent.jsx +++ b/src/components/Sidebar/SidebarContent.jsx @@ -2,6 +2,7 @@ import React from 'react'; import { SIDEBAR_TABS } from 'constants'; import HomeTab from './HomeTab'; import SearchTab from './SearchTab'; +import SoundListTab from './SoundListTab' import PathsTab from './PathsTab'; import SpacesTab from './SpacesTab'; import MidiTab from './MidiTab'; @@ -18,6 +19,8 @@ const getContentForActiveTab = (activeTab) => { return ; case SIDEBAR_TABS.SEARCH: return ; + case SIDEBAR_TABS.SOUNDLIST: + return ; case SIDEBAR_TABS.PATHS: return ; case SIDEBAR_TABS.SPACES: diff --git a/src/components/Sidebar/SidebarNavMenu.jsx b/src/components/Sidebar/SidebarNavMenu.jsx index 3417eb88..e4c45c86 100644 --- a/src/components/Sidebar/SidebarNavMenu.jsx +++ b/src/components/Sidebar/SidebarNavMenu.jsx @@ -13,6 +13,8 @@ const icons = { [SIDEBAR_TABS.HOME]: 'fa-cog', [SIDEBAR_TABS.SEARCH]: 'fa-search', [SIDEBAR_TABS.SPACES]: 'fa-th-large', +// TODO: find a new icon for list view + [SIDEBAR_TABS.SOUNDLIST]: 'fa-search', [SIDEBAR_TABS.PATHS]: 'fa-exchange', [SIDEBAR_TABS.MIDI]: 'fse-icon-synth', [SIDEBAR_TABS.INFO]: 'fa-info', diff --git a/src/components/Sidebar/SoundListTab.jsx b/src/components/Sidebar/SoundListTab.jsx new file mode 100644 index 00000000..2ed36f5d --- /dev/null +++ b/src/components/Sidebar/SoundListTab.jsx @@ -0,0 +1,15 @@ +import React from 'react'; +import './SoundListTab.scss'; +import SoundListContainer from '../../containers/Sounds/SoundListContainer'; +import baseTab from './BaseTab'; + + +// TODO: get in the real sound properties! + +const SoundListTab = () => ( +
+ +
+ ); + +export default baseTab('Sound-List', SoundListTab); \ No newline at end of file diff --git a/src/components/Sidebar/SoundListTab.scss b/src/components/Sidebar/SoundListTab.scss new file mode 100644 index 00000000..e69de29b diff --git a/src/components/Sounds/SoundList.jsx b/src/components/Sounds/SoundList.jsx new file mode 100644 index 00000000..a9068116 --- /dev/null +++ b/src/components/Sounds/SoundList.jsx @@ -0,0 +1,30 @@ +import React from 'react'; +import { lighten } from 'utils/colorsUtils'; +import './SoundList.scss'; + +const propTypes = { + sounds : React.PropTypes.array, + space: React.PropTypes.array +}; + +// TODO: get sound Objects by ID and display columns + +class SoundList extends React.Component { + constructor(props) { + super(props); + } + + render(){ + return( +
    {this.props.space.sounds.map(soundID => ( +
  • + {soundID} +
  • + ))} +
+ ); + } +} + +SoundList.propTypes = propTypes; +export default SoundList; \ No newline at end of file diff --git a/src/components/Sounds/SoundList.scss b/src/components/Sounds/SoundList.scss new file mode 100644 index 00000000..e69de29b diff --git a/src/constants.js b/src/constants.js index 7e8aca22..e6220a77 100644 --- a/src/constants.js +++ b/src/constants.js @@ -36,6 +36,7 @@ export const MESSAGE_STATUS = { export const SIDEBAR_TABS = { SEARCH: 'search', SPACES: 'spaces', + SOUNDLIST: 'soundlist', PATHS: 'paths', MIDI: 'midi', HOME: 'home', diff --git a/src/containers/Sounds/SoundListContainer.jsx b/src/containers/Sounds/SoundListContainer.jsx new file mode 100644 index 00000000..839e877d --- /dev/null +++ b/src/containers/Sounds/SoundListContainer.jsx @@ -0,0 +1,41 @@ +import React from 'react'; +import SoundList from '/components/Sounds/SoundList'; +import { connect } from 'react-redux'; +// import actions here + +const propTypes = { + spaces : React.PropTypes.array, + currentSpace: React.PropTypes.string, + sounds: React.PropTypes.array +}; + +const SoundListContainer = props => ( +
    {props.spaces.map(space => ( + + ))} +
+); + + +//class SoundListContainer extends React.Component { +// constructor(props) { +// super(props); +// } +// +// render(){ +// return ( +//
+// +//
+// ); +// }; +//}; + + +SoundListContainer.propTypes = propTypes; +const mapStateToProps = state => state.spaces; + +export default connect(mapStateToProps)(SoundListContainer); From a62426e6a936107547e5d9ef68581fcb6afdd16f Mon Sep 17 00:00:00 2001 From: Eric Lehmann Date: Fri, 9 Mar 2018 18:11:47 +0100 Subject: [PATCH 04/27] Adds new constants for keyboard shortcuts on selections --- src/constants.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/constants.js b/src/constants.js index e6220a77..c5e5391f 100644 --- a/src/constants.js +++ b/src/constants.js @@ -63,7 +63,10 @@ export const MIDI_MESSAGE_INDICATOR_DURATION = 1000; export const MIN_ZOOM = 0.05; export const MAX_ZOOM = 15; export const MAP_SCALE_FACTOR = 20; +export const ADD_SELECTION_KEYCODE = 16; // shift key +export const TOGGLE_SELECTION_KEYCODE = 17; //ctrl key export const PLAY_ON_HOVER_SHORTCUT_KEYCODE = 18; // alt key +export const TOGGLE_SELECTION_MAC_KEYCODE = 91 || 93; // cmd key left /right // tsne export const MAX_TSNE_ITERATIONS = 150; From 3ddf3be25baed2b54367061ab931d43cb4578d3d Mon Sep 17 00:00:00 2001 From: Eric Lehmann Date: Fri, 9 Mar 2018 18:14:11 +0100 Subject: [PATCH 05/27] Introduces basic table functionality including react-table and react-bootstrap-table-next > TODO: keep one - should be cleaned later --- package.json | 4 +- src/components/Sounds/SoundList.jsx | 30 ---- src/components/Sounds/SoundListItem.jsx | 130 ++++++++++++++++++ .../{SoundList.scss => SoundListItem.scss} | 0 src/containers/Sounds/SoundListContainer.jsx | 56 ++++---- 5 files changed, 160 insertions(+), 60 deletions(-) delete mode 100644 src/components/Sounds/SoundList.jsx create mode 100644 src/components/Sounds/SoundListItem.jsx rename src/components/Sounds/{SoundList.scss => SoundListItem.scss} (100%) diff --git a/package.json b/package.json index d21b96ce..a7027bf0 100644 --- a/package.json +++ b/package.json @@ -49,9 +49,11 @@ "precss": "^1.4.0", "react": "^15.3.2", "react-addons-perf": "^15.3.2", + "react-bootstrap-table-next": "^0.1.4", "react-dom": "^15.3.2", "react-hot-loader": "^3.0.0-beta.5", "react-redux": "^4.4.5", + "react-table": "^6.8.0", "redux": "^3.6.0", "redux-logger": "^2.6.1", "redux-thunk": "^2.1.0", @@ -61,7 +63,7 @@ "url-loader": "^0.5.7", "webpack": "^1.13.2", "webpack-dev-middleware": "^1.8.2", - "webpack-hot-middleware": "^2.12.1", + "webpack-hot-middleware": "^2.21.2", "why-did-you-update": "0.0.8" }, "devDependencies": { diff --git a/src/components/Sounds/SoundList.jsx b/src/components/Sounds/SoundList.jsx deleted file mode 100644 index a9068116..00000000 --- a/src/components/Sounds/SoundList.jsx +++ /dev/null @@ -1,30 +0,0 @@ -import React from 'react'; -import { lighten } from 'utils/colorsUtils'; -import './SoundList.scss'; - -const propTypes = { - sounds : React.PropTypes.array, - space: React.PropTypes.array -}; - -// TODO: get sound Objects by ID and display columns - -class SoundList extends React.Component { - constructor(props) { - super(props); - } - - render(){ - return( -
    {this.props.space.sounds.map(soundID => ( -
  • - {soundID} -
  • - ))} -
- ); - } -} - -SoundList.propTypes = propTypes; -export default SoundList; \ No newline at end of file diff --git a/src/components/Sounds/SoundListItem.jsx b/src/components/Sounds/SoundListItem.jsx new file mode 100644 index 00000000..e578d9c2 --- /dev/null +++ b/src/components/Sounds/SoundListItem.jsx @@ -0,0 +1,130 @@ +import React from 'react'; +import { lighten } from 'utils/colorsUtils'; +import './SoundListItem.scss'; +import ReactTable from 'react-table'; +import 'react-table/react-table.css'; + + +const propTypes = { + sounds : React.PropTypes.object, + space: React.PropTypes.object, +}; + +// TODO: get sound Objects by ID and display columns +// TODO: build table + +class SoundListItem extends React.Component { + constructor(props) { + super(props); + } + render(){ + const data = []; + + // only list sounds of current selected space + Object.values(this.props.space.sounds) + .forEach((id) => { + const sound = this.props.sounds[id]; + //TODO: fix variing precisions + + // format data fields + + if (sound.duration){ + sound.duration = sound.duration.toPrecision(3); + }; + if (sound.license) { + switch (sound.license) { + case "http://creativecommons.org/licenses/by/3.0/": sound.license = "CC BY 3.0"; + break; + case "http://creativecommons.org/publicdomain/zero/1.0/": + sound.license = "CC0 1.0"; + break; + case "http://creativecommons.org/licenses/by-nc/3.0/": + sound.license = "CC BY-NC 3.0"; + break; + default: + sound.license = "not specified!" + } + } + + if (sound.tags){ + sound.tags = sound.tags.sort().join(", "); + } + + data.push(sound); + }); + + const columns = [{ + Header: 'Name', + accessor: 'name', + minWidth: 150 // String-based value accessors! + },{ + Header: 'Username', + accessor: 'username', + }, {Header: 'Duration', + accessor: 'duration', +// style:{color:"red"} + + }, { + Header: 'License', + accessor: 'license', + }, +// TODO: deconcat tags + { + Header: 'Tags', + accessor: 'tags', +// Cell: props => {props.value} // Custom cell components! + } + + ] + +// { +// id: 'friendName', // Required because our accessor is not a string +// Header: 'Friend Name', +// accessor: d => d.friend.name // Custom value accessors! +// }, { +// Header: props => Friend Age, // Custom header components! +// accessor: 'friend.age' +// }] + return( + + ); + } +} + +//TODO: handle clicks on table >> must be dorpped as prop into react Table: +// getTdProps={(state, rowInfo, column, instance) => { +// return { +// onClick: (e, handleOriginal) => { +// console.log('A Td Element was clicked!') +// console.log('it produced this event:', e) +// console.log('It was in this column:', column) +// console.log('It was in this row:', rowInfo) +// console.log('It was in this table instance:', instance) +// console.log('original?', handleOriginal) +// +// // IMPORTANT! React-Table uses onClick internally to trigger +// // events like expanding SubComponents and pivots. +// // By default a custom 'onClick' handler will override this functionality. +// // If you want to fire the original onClick handler, call the +// // 'handleOriginal' function. +// if (handleOriginal) { +// handleOriginal() +// } +// } +// } +// } +// } + + +SoundListItem.propTypes = propTypes; +export default SoundListItem; \ No newline at end of file diff --git a/src/components/Sounds/SoundList.scss b/src/components/Sounds/SoundListItem.scss similarity index 100% rename from src/components/Sounds/SoundList.scss rename to src/components/Sounds/SoundListItem.scss diff --git a/src/containers/Sounds/SoundListContainer.jsx b/src/containers/Sounds/SoundListContainer.jsx index 839e877d..74755b81 100644 --- a/src/containers/Sounds/SoundListContainer.jsx +++ b/src/containers/Sounds/SoundListContainer.jsx @@ -1,41 +1,39 @@ import React from 'react'; -import SoundList from '/components/Sounds/SoundList'; +import SoundListItem from '/components/Sounds/SoundListItem'; +import { getCurrentSpace } from '../Spaces/utils' import { connect } from 'react-redux'; // import actions here const propTypes = { - spaces : React.PropTypes.array, - currentSpace: React.PropTypes.string, - sounds: React.PropTypes.array + space : React.PropTypes.object, +// currentSpace: React.PropTypes.string, + sounds: React.PropTypes.object, }; -const SoundListContainer = props => ( -
    {props.spaces.map(space => ( - - ))} -
-); - - -//class SoundListContainer extends React.Component { -// constructor(props) { -// super(props); -// } -// -// render(){ -// return ( -//
-// -//
-// ); -// }; -//}; +// Q?? class or not?! tab as container? +//const space = props => getCurrentSpace(props.spaces.spaces, props.currentSpace); +const SoundListContainer = props => + ( +
+ +
+ ); + SoundListContainer.propTypes = propTypes; -const mapStateToProps = state => state.spaces; + +function mapStateToProps(state) { + return { + space: getCurrentSpace(state.spaces.spaces, state.spaces.currentSpace), +// currentSpace: state.spaces.currentSpace, + sounds: state.sounds, + }; +} + export default connect(mapStateToProps)(SoundListContainer); From e59bb1348944f1c1b65fbcbc94e32d604ecc5780 Mon Sep 17 00:00:00 2001 From: Eric Lehmann Date: Fri, 9 Mar 2018 18:14:51 +0100 Subject: [PATCH 06/27] Gets current active space, important for last commit! --- src/containers/Spaces/utils.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/containers/Spaces/utils.js b/src/containers/Spaces/utils.js index 65f3b15f..9fcaddda 100644 --- a/src/containers/Spaces/utils.js +++ b/src/containers/Spaces/utils.js @@ -72,6 +72,14 @@ export const computeSpaceIndex = (spaces) => { return spaceIndex; }; +export const getCurrentSpace = function(spaces, queryID){ + for (var idx in spaces) { + if (spaces[idx].queryID === queryID) { + return spaces[idx] + } + } +}; + export const getSpaceDistanceToCenter = (space, center) => Math.sqrt(Math.pow((space.currentPositionInMap.x - center.x), 2) + Math.pow((space.currentPositionInMap.y - center.y), 2)); From 0ba0a47e967a7a1c7b0083c37d442d984c748d6c Mon Sep 17 00:00:00 2001 From: Eric Lehmann Date: Fri, 9 Mar 2018 18:15:23 +0100 Subject: [PATCH 07/27] Begins to take care of selection bug und multiselection feature --- src/containers/Map/MapContainer.jsx | 20 +++++++++++++++++++- src/containers/Sounds/MapCircleContainer.jsx | 6 +++++- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/src/containers/Map/MapContainer.jsx b/src/containers/Map/MapContainer.jsx index c4010a77..f7996321 100644 --- a/src/containers/Map/MapContainer.jsx +++ b/src/containers/Map/MapContainer.jsx @@ -4,7 +4,7 @@ import { zoom } from 'd3-zoom'; import { connect } from 'react-redux'; import SpaceTitle from 'components/Spaces/SpaceTitle'; import 'polyfills/requestAnimationFrame'; -import { MIN_ZOOM, MAX_ZOOM, PLAY_ON_HOVER_SHORTCUT_KEYCODE } from 'constants'; +import { MIN_ZOOM, MAX_ZOOM, PLAY_ON_HOVER_SHORTCUT_KEYCODE, TOGGLE_SELECTION_KEYCODE, ADD_SELECTION_KEYCODE } from 'constants'; import { displaySystemMessage } from '../MessagesBox/actions'; import { updateMapPosition } from './actions'; import { setSoundCurrentlyLearnt } from '../Midi/actions'; @@ -96,6 +96,24 @@ class MapContainer extends React.Component { this.props.setShouldPlayOnHover(false); } } + + +// TODO: enable add / remove from selection +// onKeydownCallback(evt) { +// if (evt.target.tagName.toUpperCase() === 'INPUT') { return; } +// if (evt.keyCode === TOGGLE_SELECTION_KEYCODE) { +// // Turn play sounds on hover on +// this.props.setShouldPlayOnHover(true); +// } +// } +// +// onKeyupCallback(evt) { +// if (evt.target.tagName.toUpperCase() === 'INPUT') { return; } +// if (evt.keyCode === TOGGLE_SELECTION_KEYCODE) { +// // Turn play sounds on hover off +// this.props.setShouldPlayOnHover(false); +// } +// } zoomHandler() { const translateX = d3Event.transform.x; diff --git a/src/containers/Sounds/MapCircleContainer.jsx b/src/containers/Sounds/MapCircleContainer.jsx index ae9c9fe6..8cd68a68 100644 --- a/src/containers/Sounds/MapCircleContainer.jsx +++ b/src/containers/Sounds/MapCircleContainer.jsx @@ -67,10 +67,14 @@ class MapCircleContainer extends React.PureComponent { } else { this.props.playAudio(this.props.sound); } + + // FIX: does not work, sound stays selected, Modal gemains open + // -> this.props.sound.isSelected is undefined!! if (this.props.sound.isSelected) { this.props.hideModal(); this.props.deselectSound(); - } else { + } + else { this.props.deselectAllSounds(); this.props.openModalForSound(this.props.sound); this.props.selectSound(this.props.sound.id); From 4c96192943375cbf61bddc6692143eb67a7aec2b Mon Sep 17 00:00:00 2001 From: Eric Lehmann Date: Mon, 12 Mar 2018 14:43:55 +0100 Subject: [PATCH 08/27] Refactors some important scripts towards eslint guidelines and readability --- .gitignore | 1 + src/polyfills/requestAnimationFrame.js | 43 +- src/vendors/freesound.js | 570 +++++++++++++------------ src/vendors/tsne.js | 366 ++++++++-------- 4 files changed, 491 insertions(+), 489 deletions(-) diff --git a/.gitignore b/.gitignore index b8764a21..b9f182a0 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,4 @@ freesound-explorer_instlog package-lock.json .python-version npm_install_log.txt +src/.vscode/launch.json diff --git a/src/polyfills/requestAnimationFrame.js b/src/polyfills/requestAnimationFrame.js index aa97c0f9..fd5dfcd2 100644 --- a/src/polyfills/requestAnimationFrame.js +++ b/src/polyfills/requestAnimationFrame.js @@ -2,30 +2,33 @@ // http://my.opera.com/emoller/blog/2011/12/20/requestanimationframe-for-smart-er-animating // requestAnimationFrame polyfill by Erik Möller. fixes from Paul Irish and Tino Zijdel +// refactored towards more eslint compatibility // MIT license -(function() { - var lastTime = 0; - var vendors = ['ms', 'moz', 'webkit', 'o']; - for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) { - window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame']; - window.cancelAnimationFrame = window[vendors[x]+'CancelAnimationFrame'] - || window[vendors[x]+'CancelRequestAnimationFrame']; - } +(function () { + let lastTime = 0; + const vendors = ['ms', 'moz', 'webkit', 'o']; + for (let x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) { + window.requestAnimationFrame = window[`${vendors[x]}RequestAnimationFrame`]; + window.cancelAnimationFrame = window[`${vendors[x]}CancelAnimationFrame`] + || window[`${vendors[x]}CancelRequestAnimationFrame`]; + } - if (!window.requestAnimationFrame) - window.requestAnimationFrame = function(callback, element) { - var currTime = new Date().getTime(); - var timeToCall = Math.max(0, 16 - (currTime - lastTime)); - var id = window.setTimeout(function() { callback(currTime + timeToCall); }, + if (!window.requestAnimationFrame) { + window.requestAnimationFrame = function (callback, element) { + const currTime = new Date().getTime(); + const timeToCall = Math.max(0, 16 - (currTime - lastTime)); + const id = window.setTimeout(() => { callback(currTime + timeToCall); }, timeToCall); - lastTime = currTime + timeToCall; - return id; - }; + lastTime = currTime + timeToCall; + return id; + }; + } - if (!window.cancelAnimationFrame) - window.cancelAnimationFrame = function(id) { - clearTimeout(id); - }; + if (!window.cancelAnimationFrame) { + window.cancelAnimationFrame = function (id) { + clearTimeout(id); + }; + } }()); diff --git a/src/vendors/freesound.js b/src/vendors/freesound.js index febe9e0c..7ed4f89f 100644 --- a/src/vendors/freesound.js +++ b/src/vendors/freesound.js @@ -1,318 +1,326 @@ const freesound = (function () { - var authHeader = ''; - var clientId = ''; - var clientSecret = ''; - var host = 'freesound.org'; - - var uris = { - base : 'https://'+host+'/apiv2', - textSearch : '/search/text/', + let authHeader = ''; + let clientId = ''; + let clientSecret = ''; + const host = 'freesound.org'; + + const uris = { + base: `https://${host}/apiv2`, + textSearch: '/search/text/', contentSearch: '/search/content/', - combinedSearch : '/sounds/search/combined/', - sound : '/sounds//', - soundAnalysis : '/sounds//analysis/', - similarSounds : '/sounds//similar/', - comments : '/sounds//comments/', - download : '/sounds//download/', - upload : '/sounds/upload/', - describe : '/sounds//describe/', - pending : '/sounds/pending_uploads/', - bookmark : '/sounds//bookmark/', - rate : '/sounds//rate/', - comment : '/sounds//comment/', - authorize : '/oauth2/authorize/', - logout : '/api-auth/logout/', - logoutAuthorize : '/oauth2/logout_and_authorize/', - me : '/me/', - user : '/users//', - userSounds : '/users//sounds/', - userPacks : '/users//packs/', - userBookmarkCategories : '/users//bookmark_categories/', - userBookmarkCategorySounds : '/users//bookmark_categories//sounds/', - pack : '/packs//', - packSounds : '/packs//sounds/', - packDownload : '/packs//download/' + combinedSearch: '/sounds/search/combined/', + sound: '/sounds//', + soundAnalysis: '/sounds//analysis/', + similarSounds: '/sounds//similar/', + comments: '/sounds//comments/', + download: '/sounds//download/', + upload: '/sounds/upload/', + describe: '/sounds//describe/', + pending: '/sounds/pending_uploads/', + bookmark: '/sounds//bookmark/', + rate: '/sounds//rate/', + comment: '/sounds//comment/', + authorize: '/oauth2/authorize/', + logout: '/api-auth/logout/', + logoutAuthorize: '/oauth2/logout_and_authorize/', + me: '/me/', + user: '/users//', + userSounds: '/users//sounds/', + userPacks: '/users//packs/', + userBookmarkCategories: '/users//bookmark_categories/', + userBookmarkCategorySounds: '/users//bookmark_categories//sounds/', + pack: '/packs//', + packSounds: '/packs//sounds/', + packDownload: '/packs//download/', }; - var makeUri = function (uri, args){ - for (var a in args) {uri = uri.replace(/<[\w_]+>/, args[a]);} - return uris.base+uri; + const makeUri = function (uri, args) { + for (const a in args) { + uri = uri.replace(/<[\w_]+>/, args[a]); + } + return uris.base + uri; }; - var makeRequest = function (uri, success, error, params, wrapper, method, data, content_type){ + const makeRequest = function (uri, success, error, params, wrapper, method, data, contentType) { return new Promise((resolve, failure) => { - if(method===undefined) method='GET'; - if(!error)error = function(e){console.log(e)}; + if (method === undefined) method = 'GET'; + if (!error)error = function (e) { console.log(e); }; params = params || {}; - params['format'] = 'json'; - var fs = this; - var paramStr = ""; - for(var p in params){paramStr = paramStr+"&"+p+"="+params[p];} - if (paramStr){ - uri = uri +"?"+ paramStr; + params.format = 'json'; + let paramStr = ''; + for (const p in params) { paramStr = `${paramStr}&${p}=${params[p]}`; } + if (paramStr) { + uri = `${uri}?${paramStr}`; } - var xhr; - try {xhr = new XMLHttpRequest();} - catch (e) {xhr = new ActiveXObject('Microsoft.XMLHTTP');} - - xhr.onreadystatechange = function(){ - if (xhr.readyState === 4 && [200,201,202].indexOf(xhr.status)>=0){ - var data = eval("(" + xhr.responseText + ")"); - resolve(wrapper?wrapper(data):data); - } - else if (xhr.readyState === 4 && xhr.status !== 200){ - failure(xhr.statusText); - } + let xhr; + try { + xhr = new XMLHttpRequest(); + } catch (e) { + xhr = new ActiveXObject('Microsoft.XMLHTTP'); + } + + xhr.onreadystatechange = function () { + if (xhr.readyState === 4 && [200, 201, 202].indexOf(xhr.status) >= 0) { + const data = eval(`(${xhr.responseText})`); + resolve(wrapper ? wrapper(data) : data); + } else if (xhr.readyState === 4 && xhr.status !== 200) { + failure(xhr.statusText); + } }; xhr.open(method, uri); - xhr.setRequestHeader('Authorization',authHeader); - if(content_type!==undefined) - xhr.setRequestHeader('Content-Type',content_type); + xhr.setRequestHeader('Authorization', authHeader); + if (contentType !== undefined) { xhr.setRequestHeader('Content-Type', contentType); } xhr.send(data); - }) - + }); }; - var checkOauth = function(){ - if(authHeader.indexOf("Bearer")==-1) - throw("Oauth authentication required"); + const checkOauth = function () { + if (authHeader.indexOf('Bearer') === -1) { + throw ('Oauth authentication required'); + } }; - var makeFD = function(obj,fd){ - if(!fd) - fd = new FormData(); - for (var prop in obj){ - fd.append(prop,obj[prop]) - } - return fd; + const makeFD = function (obj, fd) { + if (!fd) { + fd = new FormData(); + } + for (const prop in obj) { + fd.append(prop, obj[prop]); + } + return fd; }; - var searchFS = function(options, uri, success, error,wrapper){ - if(options.analysis_file){ - makeRequest(makeUri(uri), success,error,null, wrapper, 'POST',makeFD(options)); - } - else{ - return makeRequest(makeUri(uri), success,error,options, wrapper); - } + const searchFS = function (options, uri, success, error, wrapper) { + if (options.analysis_file) { + makeRequest(makeUri(uri), success, error, null, wrapper, 'POST', makeFD(options)); + } else { + return makeRequest(makeUri(uri), success, error, options, wrapper); + } }; - var Collection = function (jsonObject){ - var nextOrPrev = function (which,success,error){ - makeRequest(which,success,error,{}, Collection); - }; - jsonObject.nextPage = function (success,error){ - nextOrPrev(jsonObject.next,success,error); - }; - jsonObject.previousPage = function (success,error){ - nextOrPrev(jsonObject.previous,success,error); - }; - jsonObject.getItem = function (idx){ - return jsonObject.results[idx]; - } - - return jsonObject; + const Collection = function (jsonObject) { + const nextOrPrev = function (which, success, error) { + makeRequest(which, success, error, {}, Collection); + }; + jsonObject.nextPage = function (success, error) { + nextOrPrev(jsonObject.next, success, error); + }; + jsonObject.previousPage = function (success, error) { + nextOrPrev(jsonObject.previous, success, error); + }; + jsonObject.getItem = function (idx) { + return jsonObject.results[idx]; + }; + + return jsonObject; }; - var SoundCollection = function(jsonObject){ - var collection = Collection(jsonObject); - collection.getSound = function (idx){ - return new SoundObject(collection.results[idx]); - }; - return collection; + const SoundCollection = function (jsonObject) { + const collection = Collection(jsonObject); + collection.getSound = function (idx) { + return new SoundObject(collection.results[idx]); + }; + return collection; }; - var PackCollection = function(jsonObject){ - var collection = Collection(jsonObject); - collection.getPack = function (idx){ - return new PackObject(collection.results[idx]); - }; - return collection; + const PackCollection = function (jsonObject) { + const collection = Collection(jsonObject); + collection.getPack = function (idx) { + return new PackObject(collection.results[idx]); + }; + return collection; }; - var SoundObject = function (jsonObject){ - jsonObject.getAnalysis = function(filter, success, error, showAll){ - var params = {all: showAll?1:0}; - makeRequest(makeUri(uris.soundAnalysis,[jsonObject.id,filter?filter:""]),success,error); - }; - - jsonObject.getSimilar = function (success, error, params){ - makeRequest(makeUri(uris.similarSounds,[jsonObject.id]),success,error, params,SoundCollection); - }; - - jsonObject.getComments = function (success, error){ - makeRequest(makeUri(uris.comments,[jsonObject.id]),success,error,{},Collection); - }; - - jsonObject.download = function (targetWindow){// can be window, new, or iframe - checkOauth(); - var uri = makeUri(uris.download,[jsonObject.id]); - targetWindow.location = uri; - }; - - jsonObject.comment = function (commentStr, success, error){ - checkOauth(); - var data = new FormData(); - data.append('comment', comment); - var uri = makeUri(uris.comment,[jsonObject.id]); - makeRequest(uri, success, error, {}, null, 'POST', data); - }; - - jsonObject.rate = function (rating, success, error){ - checkOauth(); - var data = new FormData(); - data.append('rating', rating); - var uri = makeUri(uris.rate,[jsonObject.id]); - makeRequest(uri, success, error, {}, null, 'POST', data); - }; - - jsonObject.bookmark = function (name, category,success, error){ - checkOauth(); - var data = new FormData(); - data.append('name', name); - if(category) - data.append("category",category); - var uri = makeUri(uris.bookmark,[jsonObject.id]); - return makeRequest(uri, success, error, {}, null, 'POST', data); - }; - - jsonObject.edit = function (description,success, error){ - checkOauth(); - var data = makeFD(description); - var uri = makeUri(uris.edit,[jsonObject.id]); - makeRequest(uri, success, error, {}, null, 'POST', data); - }; - - return jsonObject; + let SoundObject = function (jsonObject) { + jsonObject.getAnalysis = function (filter, success, error, showAll) { + const params = { all: showAll ? 1 : 0 }; + makeRequest(makeUri(uris.soundAnalysis, [jsonObject.id, filter || '']), success, error); + }; + + jsonObject.getSimilar = function (success, error, params) { + makeRequest( + makeUri(uris.similarSounds, [jsonObject.id]), + success, error, params, SoundCollection + ); + }; + + jsonObject.getComments = function (success, error) { + makeRequest(makeUri(uris.comments, [jsonObject.id]), success, error, {}, Collection); + }; + + jsonObject.download = function (targetWindow) { // can be window, new, or iframe + checkOauth(); + const uri = makeUri(uris.download, [jsonObject.id]); + targetWindow.location = uri; + }; + + jsonObject.comment = function (commentStr, success, error) { + checkOauth(); + const data = new FormData(); + data.append('comment', commentStr); + const uri = makeUri(uris.comment, [jsonObject.id]); + makeRequest(uri, success, error, {}, null, 'POST', data); + }; + + jsonObject.rate = function (rating, success, error) { + checkOauth(); + const data = new FormData(); + data.append('rating', rating); + const uri = makeUri(uris.rate, [jsonObject.id]); + makeRequest(uri, success, error, {}, null, 'POST', data); + }; + + jsonObject.bookmark = function (name, category, success, error) { + checkOauth(); + const data = new FormData(); + data.append('name', name); + if (category) { + data.append('category', category); + } + const uri = makeUri(uris.bookmark, [jsonObject.id]); + return makeRequest(uri, success, error, {}, null, 'POST', data); + }; + + jsonObject.edit = function (description, success, error) { + checkOauth(); + const data = makeFD(description); + const uri = makeUri(uris.edit, [jsonObject.id]); + makeRequest(uri, success, error, {}, null, 'POST', data); + }; + + return jsonObject; }; - var UserObject = function(jsonObject){ - jsonObject.sounds = function (success, error, params){ - var uri = makeUri(uris.userSounds,[jsonObject.username]); - makeRequest(uri, success, error,params,SoundCollection); - }; - - jsonObject.packs = function (success, error){ - var uri = makeUri(uris.userPacks,[jsonObject.username]); - makeRequest(uri, success, error,{},PackCollection); - }; - - jsonObject.bookmarkCategories = function (success, error){ - var uri = makeUri(uris.userBookmarkCategories,[jsonObject.username]); - makeRequest(uri, success, error); - }; - - jsonObject.bookmarkCategorySounds = function (success, error,params){ - var uri = makeUri(uris.userBookmarkCategorySounds,[jsonObject.username]); - makeRequest(uri, success, error,params); - }; - - return jsonObject; + const UserObject = function (jsonObject) { + jsonObject.sounds = function (success, error, params) { + const uri = makeUri(uris.userSounds, [jsonObject.username]); + makeRequest(uri, success, error, params, SoundCollection); + }; + + jsonObject.packs = function (success, error) { + const uri = makeUri(uris.userPacks, [jsonObject.username]); + makeRequest(uri, success, error, {}, PackCollection); + }; + + jsonObject.bookmarkCategories = function (success, error) { + const uri = makeUri(uris.userBookmarkCategories, [jsonObject.username]); + makeRequest(uri, success, error); + }; + + jsonObject.bookmarkCategorySounds = function (success, error, params) { + const uri = makeUri(uris.userBookmarkCategorySounds, [jsonObject.username]); + makeRequest(uri, success, error, params); + }; + + return jsonObject; }; - var PackObject = function(jsonObject){ - jsonObject.sounds = function (success, error){ - var uri = makeUri(uris.packSounds,[jsonObject.id]); - makeRequest(uri, success, error,{},SoundCollection); - }; - - jsonObject.download = function (targetWindow){// can be current or new window, or iframe - checkOauth(); - var uri = makeUri(uris.packDownload,[jsonObject.id]); - targetWindow.location = uri; - }; - return jsonObject; + const PackObject = function (jsonObject) { + jsonObject.sounds = function (success, error) { + const uri = makeUri(uris.packSounds, [jsonObject.id]); + makeRequest(uri, success, error, {}, SoundCollection); + }; + + jsonObject.download = function (targetWindow) { // can be current or new window, or iframe + checkOauth(); + const uri = makeUri(uris.packDownload, [jsonObject.id]); + targetWindow.location = uri; + }; + return jsonObject; }; return { // authentication - setToken: function (token, type) { - authHeader = (type==='oauth' ? 'Bearer ':'Token ')+token; - }, - setClientSecrets: function(id,secret){ - clientId = id; - clientSecret = secret; - }, - - postAccessCode: function(code, success, error){ - var post_url = uris.base+"/oauth2/access_token/" - var data = new FormData(); - data.append('client_id',clientId); - data.append('client_secret',clientSecret); - data.append('code',code); - data.append('grant_type','authorization_code'); - - if (!success){ - success = function(result){ - setToken(result.access_token,'oauth'); - } - } - makeRequest(post_url, success, error, {}, null, 'POST', data); - }, - textSearch: function(query, options, success, error){ - options = options || {}; - options.query = query ? query : " "; - return searchFS(options,uris.textSearch,success,error,SoundCollection); - }, - contentSearch: function(options, success, error){ - if(!(options.target || options.analysis_file)) - throw("Missing target or analysis_file"); - searchFS(options,uris.contentSearch,success,error,SoundCollection); - }, - combinedSearch:function(options, success, error){ - if(!(options.target || options.analysis_file || options.query)) - throw("Missing query, target or analysis_file"); - searchFS(options,uris.contentSearch,success,error); - }, - getSound: function(soundId,success, error){ - makeRequest(makeUri(uris.sound, [soundId]), success,error,{}, SoundObject); - }, - - upload: function(audiofile,filename, description, success,error){ - checkOauth(); - var fd = new FormData(); - fd.append('audiofile', audiofile,filename); - if(description){ - fd = makeFD(description,fd); - } - makeRequest(makeUri(uris.upload), success, error, {}, null, 'POST', fd); - }, - describe: function(upload_filename , description, license, tags, success,error){ - checkOauth(); - var fd = makeFD(description); - makeRequest(makeUri(uris.upload), success, error, {}, null, 'POST', fd); - }, - - getPendingSounds: function(success,error){ - checkOauth(); - makeRequest(makeUri(uris.pending), success,error,{}); - }, - - // user resources - me: function(success,error){ - checkOauth(); - makeRequest(makeUri(uris.me), success,error); - }, - - getLoginURL: function(){ - if(clientId===undefined) throw "client_id was not set" - var login_url = makeUri(uris.authorize); - login_url += "?client_id="+clientId+"&response_type=code"; - return login_url; - }, - getLogoutURL: function(){ - var logout_url = makeUri(uris.logoutAuthorize); - logout_url += "?client_id="+clientId+"&response_type=code"; - - return logout_url; - }, - - getUser: function(username, success,error){ - makeRequest(makeUri(uris.user, [username]), success,error,{}, UserObject); - }, - - getPack: function(packId,success,error){ - makeRequest(makeUri(uris.pack, [packId]), success,error,{}, PackObject); - } + setToken(token, type) { + authHeader = (type === 'oauth' ? 'Bearer ' : 'Token ') + token; + }, + setClientSecrets(id, secret) { + clientId = id; + clientSecret = secret; + }, + + postAccessCode(code, success, error) { + const post_url = `${uris.base}/oauth2/access_token/`; + const data = new FormData(); + data.append('client_id', clientId); + data.append('client_secret', clientSecret); + data.append('code', code); + data.append('grant_type', 'authorization_code'); + + if (!success) { + success = function (result) { + setToken(result.access_token, 'oauth'); + }; + } + makeRequest(post_url, success, error, {}, null, 'POST', data); + }, + textSearch(query, options, success, error) { + options = options || {}; + options.query = query || ' '; + return searchFS(options, uris.textSearch, success, error, SoundCollection); + }, + contentSearch(options, success, error) { + if (!(options.target || options.analysis_file)) { + throw ('Missing target or analysis_file'); + } + searchFS(options, uris.contentSearch, success, error, SoundCollection); + }, + combinedSearch(options, success, error) { + if (!(options.target || options.analysis_file || options.query)) { + throw ('Missing query, target or analysis_file'); } + searchFS(options, uris.contentSearch, success, error); + }, + getSound(soundId, success, error) { + makeRequest(makeUri(uris.sound, [soundId]), success, error, {}, SoundObject); + }, + + upload(audiofile, filename, description, success, error) { + checkOauth(); + let fd = new FormData(); + fd.append('audiofile', audiofile, filename); + if (description) { + fd = makeFD(description, fd); + } + makeRequest(makeUri(uris.upload), success, error, {}, null, 'POST', fd); + }, + describe(upload_filename, description, license, tags, success, error) { + checkOauth(); + const fd = makeFD(description); + makeRequest(makeUri(uris.upload), success, error, {}, null, 'POST', fd); + }, + + getPendingSounds(success, error) { + checkOauth(); + makeRequest(makeUri(uris.pending), success, error, {}); + }, + + // user resources + me(success, error) { + checkOauth(); + makeRequest(makeUri(uris.me), success, error); + }, + + getLoginURL() { + if (clientId === undefined) throw 'client_id was not set'; + let login_url = makeUri(uris.authorize); + login_url += `?client_id=${clientId}&response_type=code`; + return login_url; + }, + getLogoutURL() { + let logout_url = makeUri(uris.logoutAuthorize); + logout_url += `?client_id=${clientId}&response_type=code`; + + return logout_url; + }, + + getUser(username, success, error) { + makeRequest(makeUri(uris.user, [username]), success, error, {}, UserObject); + }, + + getPack(packId, success, error) { + makeRequest(makeUri(uris.pack, [packId]), success, error, {}, PackObject); + }, + }; }()); export default freesound; diff --git a/src/vendors/tsne.js b/src/vendors/tsne.js index 6dc3eae4..bc22b73c 100644 --- a/src/vendors/tsne.js +++ b/src/vendors/tsne.js @@ -2,68 +2,64 @@ // https://github.com/karpathy/tsnejs // create main global object -var tsnejs = tsnejs || { REVISION: 'ALPHA' }; - -(function(global) { - "use strict"; +let tsnejs = tsnejs || { REVISION: 'ALPHA' }; +(function (global) { // utility function - var assert = function(condition, message) { - if (!condition) { throw message || "Assertion failed"; } - } + const assert = function (condition, message) { + if (!condition) { throw message || 'Assertion failed'; } + }; // syntax sugar - var getopt = function(opt, field, defaultval) { - if(opt.hasOwnProperty(field)) { + const getopt = function (opt, field, defaultval) { + if (opt.hasOwnProperty(field)) { return opt[field]; - } else { - return defaultval; } - } + return defaultval; + }; // return 0 mean unit standard deviation random number - var return_v = false; - var v_val = 0.0; - var gaussRandom = function() { - if(return_v) { + let return_v = false; + let v_val = 0.0; + const gaussRandom = function () { + if (return_v) { return_v = false; return v_val; } - var u = 2*Math.random()-1; - var v = 2*Math.random()-1; - var r = u*u + v*v; - if(r == 0 || r > 1) return gaussRandom(); - var c = Math.sqrt(-2*Math.log(r)/r); - v_val = v*c; // cache this for next function call for efficiency + const u = 2 * Math.random() - 1; + const v = 2 * Math.random() - 1; + const r = u * u + v * v; + if (r === 0 || r > 1) return gaussRandom(); + const c = Math.sqrt(-2 * Math.log(r) / r); + v_val = v * c; // cache this for next function call for efficiency return_v = true; - return u*c; - } + return u * c; + }; // return random normal number - var randn = function(mu, std){ return mu+gaussRandom()*std; } + const randn = function (mu, std) { return mu + gaussRandom() * std; }; // utilitity that creates contiguous vector of zeros of size n - var zeros = function(n) { - if(typeof(n)==='undefined' || isNaN(n)) { return []; } - if(typeof ArrayBuffer === 'undefined') { + const zeros = function (n) { + if (typeof (n) === 'undefined' || isNaN(n)) { return []; } + if (typeof ArrayBuffer === 'undefined') { // lacking browser support - var arr = new Array(n); - for(var i=0;i 1e-7) Hhere -= pj * Math.log(pj); + if (pj > 1e-7) Hhere -= pj * Math.log(pj); } // adjust beta based on result - if(Hhere > Htarget) { + if (Hhere > Htarget) { // entropy was too high (distribution too diffuse) // so we need to increase the precision for more peaky distribution betamin = beta; // move up the bounds - if(betamax === Infinity) { beta = beta * 2; } - else { beta = (beta + betamax) / 2; } - + if (betamax === Infinity) { beta *= 2; } else { beta = (beta + betamax) / 2; } } else { // converse case. make distrubtion less peaky betamax = beta; - if(betamin === -Infinity) { beta = beta / 2; } - else { beta = (beta + betamin) / 2; } + if (betamin === -Infinity) { beta /= 2; } else { beta = (beta + betamin) / 2; } } // stopping conditions: too many tries or got a good precision num++; - if(Math.abs(Hhere - Htarget) < tol) { done = true; } - if(num >= maxtries) { done = true; } + if (Math.abs(Hhere - Htarget) < tol) { done = true; } + if (num >= maxtries) { done = true; } } // console.log('data point ' + i + ' gets precision ' + beta + ' after ' + num + ' binary search steps.'); // copy over the final prow to P at row i - for(var j=0;j 0 ? 1 : x < 0 ? -1 : 0; } - var tSNE = function(opt) { - var opt = opt || {}; - this.perplexity = getopt(opt, "perplexity", 30); // effective number of nearest neighbors - this.dim = getopt(opt, "dim", 2); // by default 2-D tSNE - this.epsilon = getopt(opt, "epsilon", 10); // learning rate + const tSNE = function (opt) { + opt = opt || {}; + this.perplexity = getopt(opt, 'perplexity', 30); // effective number of nearest neighbors + this.dim = getopt(opt, 'dim', 2); // by default 2-D tSNE + this.epsilon = getopt(opt, 'epsilon', 10); // learning rate this.iter = 0; - } + }; tSNE.prototype = { // this function takes a set of high-dimensional points // and creates matrix P from them using gaussian kernel - initDataRaw: function(X) { - var N = X.length; - var D = X[0].length; - assert(N > 0, " X is empty? You must have some data!"); - assert(D > 0, " X[0] is empty? Where is the data?"); - var dists = xtod(X); // convert X to distances using gaussian kernel + initDataRaw(X) { + const N = X.length; + const D = X[0].length; + assert(N > 0, ' X is empty? You must have some data!'); + assert(D > 0, ' X[0] is empty? Where is the data?'); + const dists = xtod(X); // convert X to distances using gaussian kernel this.P = d2p(dists, this.perplexity, 1e-4); // attach to object this.N = N; // back up the size of the dataset this.initSolution(); // refresh this @@ -207,16 +197,16 @@ var tsnejs = tsnejs || { REVISION: 'ALPHA' }; // this function takes a given distance matrix and creates // matrix P from them. // D is assumed to be provided as a list of lists, and should be symmetric - initDataDist: function(D) { - var N = D.length; - assert(N > 0, " X is empty? You must have some data!"); + initDataDist(D) { + const N = D.length; + assert(N > 0, ' X is empty? You must have some data!'); // convert D to a (fast) typed array version - var dists = zeros(N * N); // allocate contiguous array - for(var i=0;i Date: Mon, 12 Mar 2018 14:44:46 +0100 Subject: [PATCH 09/27] Fixes selection and modal closing bug --- src/containers/Sounds/MapCircleContainer.jsx | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/containers/Sounds/MapCircleContainer.jsx b/src/containers/Sounds/MapCircleContainer.jsx index 8cd68a68..2c19dbdf 100644 --- a/src/containers/Sounds/MapCircleContainer.jsx +++ b/src/containers/Sounds/MapCircleContainer.jsx @@ -67,15 +67,16 @@ class MapCircleContainer extends React.PureComponent { } else { this.props.playAudio(this.props.sound); } - - // FIX: does not work, sound stays selected, Modal gemains open - // -> this.props.sound.isSelected is undefined!! - if (this.props.sound.isSelected) { + + // FIXED: -> this.props.sound.isSelected was undefined!! + // changed to this.props.isSelected as it seems to hold the proper value + // FIX: Modal does not open again when sound is selected + if (this.props.isSelected) { this.props.hideModal(); this.props.deselectSound(); - } + } else { - this.props.deselectAllSounds(); + this.props.deselectAllSounds(); // does not work on sounds.selectedSounds this.props.openModalForSound(this.props.sound); this.props.selectSound(this.props.sound.id); } From f5f4361cb7209f916ac2d2ce2245db438e609f3f Mon Sep 17 00:00:00 2001 From: Eric Lehmann Date: Tue, 13 Mar 2018 13:34:14 +0100 Subject: [PATCH 10/27] Adds new item for soundlist to navbar --- src/components/Sidebar/SidebarNavMenu.jsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/components/Sidebar/SidebarNavMenu.jsx b/src/components/Sidebar/SidebarNavMenu.jsx index e4c45c86..ad08afe5 100644 --- a/src/components/Sidebar/SidebarNavMenu.jsx +++ b/src/components/Sidebar/SidebarNavMenu.jsx @@ -13,8 +13,7 @@ const icons = { [SIDEBAR_TABS.HOME]: 'fa-cog', [SIDEBAR_TABS.SEARCH]: 'fa-search', [SIDEBAR_TABS.SPACES]: 'fa-th-large', -// TODO: find a new icon for list view - [SIDEBAR_TABS.SOUNDLIST]: 'fa-search', + [SIDEBAR_TABS.SOUNDLIST]: 'fa-align-justify', [SIDEBAR_TABS.PATHS]: 'fa-exchange', [SIDEBAR_TABS.MIDI]: 'fse-icon-synth', [SIDEBAR_TABS.INFO]: 'fa-info', From 147b7b56d88f2e863f56c4da4faacfa807cb204c Mon Sep 17 00:00:00 2001 From: Eric Lehmann Date: Tue, 13 Mar 2018 14:21:49 +0100 Subject: [PATCH 11/27] Fixes formatting errors and adds new license aliases --- src/components/Sounds/SoundListItem.jsx | 127 +++++++++++++----------- 1 file changed, 71 insertions(+), 56 deletions(-) diff --git a/src/components/Sounds/SoundListItem.jsx b/src/components/Sounds/SoundListItem.jsx index e578d9c2..693c7a43 100644 --- a/src/components/Sounds/SoundListItem.jsx +++ b/src/components/Sounds/SoundListItem.jsx @@ -6,7 +6,7 @@ import 'react-table/react-table.css'; const propTypes = { - sounds : React.PropTypes.object, + sounds: React.PropTypes.object, space: React.PropTypes.object, }; @@ -15,68 +15,83 @@ const propTypes = { class SoundListItem extends React.Component { constructor(props) { - super(props); + super(props); } - render(){ + render() { const data = []; - + // only list sounds of current selected space Object.values(this.props.space.sounds) .forEach((id) => { - const sound = this.props.sounds[id]; - //TODO: fix variing precisions - + const sound = this.props.sounds[id]; + // console.log(sound.duration.toFixed(2)); + + // TODO: fix variing precisions + // format data fields - - if (sound.duration){ - sound.duration = sound.duration.toPrecision(3); - }; - if (sound.license) { - switch (sound.license) { - case "http://creativecommons.org/licenses/by/3.0/": sound.license = "CC BY 3.0"; - break; - case "http://creativecommons.org/publicdomain/zero/1.0/": - sound.license = "CC0 1.0"; - break; - case "http://creativecommons.org/licenses/by-nc/3.0/": - sound.license = "CC BY-NC 3.0"; - break; - default: - sound.license = "not specified!" + // debugger; + if (sound.duration) { + sound.durationfixed = sound.duration.toFixed(2); } - } - + if (sound.license) { + switch (sound.license) { + case 'http://creativecommons.org/licenses/by/3.0/': + sound.shortLicense = 'CC BY 3.0'; + break; + case 'http://creativecommons.org/publicdomain/zero/1.0/': + sound.shortLicense = 'CC0 1.0'; + break; + case 'http://creativecommons.org/licenses/by-nc/3.0/': + sound.shortLicense = 'CC BY-NC 3.0'; + break; + case 'http://creativecommons.org/licenses/by-nc/4.0/': + sound.shortLicense = 'CC BY-NC 4.0'; + break; + case 'http://creativecommons.org/licenses/sampling+/1.0/': + sound.shortLicense = 'Sampling Plus 1.0'; + break; + case 'http://creativecommons.org/licenses/by-sa/4.0/': + sound.shortLicense = 'CC BY-SA 4.0'; + break; + case 'http://creativecommons.org/licenses/by-nd/4.0/': + sound.shortLicense = 'CC BY-ND 4.0'; + break; + default: + sound.shortLicense = 'not specified!'; + } + } + if (sound.tags){ - sound.tags = sound.tags.sort().join(", "); + sound.tagsStr = sound.tags.sort().join(', '); } - - data.push(sound); - }); - + + data.push(sound); + }); + const columns = [{ - Header: 'Name', - accessor: 'name', - minWidth: 150 // String-based value accessors! - },{ - Header: 'Username', - accessor: 'username', - }, {Header: 'Duration', - accessor: 'duration', + Header: 'Name', + accessor: 'name', + minWidth: 150, // String-based value accessors! + }, { + Header: 'Username', + accessor: 'username', + }, { Header: 'Duration', + accessor: 'durationfixed', // style:{color:"red"} - - }, { - Header: 'License', - accessor: 'license', - }, + + }, { + Header: 'License', + accessor: 'shortLicense', + }, // TODO: deconcat tags - { - Header: 'Tags', - accessor: 'tags', + { + Header: 'Tags', + accessor: 'tagsStr', // Cell: props => {props.value} // Custom cell components! - } - - ] - + }, + + ]; + // { // id: 'friendName', // Required because our accessor is not a string // Header: 'Friend Name', @@ -85,12 +100,12 @@ class SoundListItem extends React.Component { // Header: props => Friend Age, // Custom header components! // accessor: 'friend.age' // }] - return( + return ( > must be dorpped as prop into react Table: +// TODO: handle clicks on table >> must be dorpped as prop into react Table: // getTdProps={(state, rowInfo, column, instance) => { // return { // onClick: (e, handleOriginal) => { @@ -127,4 +142,4 @@ class SoundListItem extends React.Component { SoundListItem.propTypes = propTypes; -export default SoundListItem; \ No newline at end of file +export default SoundListItem; From fe768702487df23671abfb31722bd56bf1539ffd Mon Sep 17 00:00:00 2001 From: Eric Lehmann Date: Wed, 14 Mar 2018 10:29:19 +0100 Subject: [PATCH 12/27] Fixes crash when no sounds are loaded into SoundList - but message always shows up - some additional cosmetic --- src/components/Sounds/SoundListItem.jsx | 21 +++++++------------- src/containers/Sounds/MapCircleContainer.jsx | 3 +-- src/containers/Sounds/SoundListContainer.jsx | 21 ++++++++++++-------- 3 files changed, 21 insertions(+), 24 deletions(-) diff --git a/src/components/Sounds/SoundListItem.jsx b/src/components/Sounds/SoundListItem.jsx index 693c7a43..b3230376 100644 --- a/src/components/Sounds/SoundListItem.jsx +++ b/src/components/Sounds/SoundListItem.jsx @@ -1,9 +1,8 @@ import React from 'react'; -import { lighten } from 'utils/colorsUtils'; -import './SoundListItem.scss'; import ReactTable from 'react-table'; import 'react-table/react-table.css'; - +import { lighten } from 'utils/colorsUtils'; +import './SoundListItem.scss'; const propTypes = { sounds: React.PropTypes.object, @@ -24,12 +23,7 @@ class SoundListItem extends React.Component { Object.values(this.props.space.sounds) .forEach((id) => { const sound = this.props.sounds[id]; - // console.log(sound.duration.toFixed(2)); - - // TODO: fix variing precisions - - // format data fields - // debugger; + // format data fields if (sound.duration) { sound.durationfixed = sound.duration.toFixed(2); } @@ -60,11 +54,9 @@ class SoundListItem extends React.Component { sound.shortLicense = 'not specified!'; } } - - if (sound.tags){ - sound.tagsStr = sound.tags.sort().join(', '); - } - + if (sound.tags) { + sound.tagsStr = sound.tags.sort().join(', '); + } data.push(sound); }); @@ -100,6 +92,7 @@ class SoundListItem extends React.Component { // Header: props => Friend Age, // Custom header components! // accessor: 'friend.age' // }] + return ( getCurrentSpace(props.spaces.spaces, props.currentSpace); - +// const space = props => getCurrentSpace(props.spaces.spaces, props.currentSpace); +// TODO: update error message const SoundListContainer = props => ( -
+
+ {typeof props.sounds.byID.length !== 'undefined' ? null : +
No sounds to list, please search first!
} + {typeof props.space === 'undefined' ? null : +
+ /> +
}
); From 9e7cc8fb6127c4439db01d76e394a0a72c0c4bd7 Mon Sep 17 00:00:00 2001 From: Eric Lehmann Date: Wed, 14 Mar 2018 10:35:52 +0100 Subject: [PATCH 13/27] Changes SpaceThumbnail Duration format to something less confusing --- src/components/Spaces/SpaceTitle.jsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/components/Spaces/SpaceTitle.jsx b/src/components/Spaces/SpaceTitle.jsx index aaeeee3f..8936affb 100644 --- a/src/components/Spaces/SpaceTitle.jsx +++ b/src/components/Spaces/SpaceTitle.jsx @@ -45,8 +45,7 @@ class SpaceTitle extends React.Component {
  • Arranged by { (this.props.queryParams.descriptor) === 'lowlevel.mfcc.mean' ? 'Timbre' : 'Tonality'}
  • -
  • Duration: [{this.props.queryParams.minDuration}, - {this.props.queryParams.maxDuration}]s
  • +
  • Duration: {this.props.queryParams.minDuration} to {this.props.queryParams.maxDuration} s
  • ); } From f7e0225e781406b4994cd492bcb02fb659cb7960 Mon Sep 17 00:00:00 2001 From: Eric Lehmann Date: Wed, 14 Mar 2018 11:06:10 +0100 Subject: [PATCH 14/27] Fixes SoundInfoModal behaviour: reopens also when already selected --- src/containers/Sounds/MapCircleContainer.jsx | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/containers/Sounds/MapCircleContainer.jsx b/src/containers/Sounds/MapCircleContainer.jsx index 6991b6b3..f934ae4a 100644 --- a/src/containers/Sounds/MapCircleContainer.jsx +++ b/src/containers/Sounds/MapCircleContainer.jsx @@ -21,6 +21,7 @@ const propTypes = { toggleHoveringSound: React.PropTypes.func, openModalForSound: React.PropTypes.func, hideModal: React.PropTypes.func, + isSoundModalVisible: React.PropTypes.bool, }; const isSoundVisible = (props) => { @@ -68,11 +69,12 @@ class MapCircleContainer extends React.PureComponent { this.props.playAudio(this.props.sound); } - // FIXED: -> this.props.sound.isSelected was undefined!! - // changed to this.props.isSelected as it seems to hold the proper value - // FIX: Modal does not open again when sound is selected if (this.props.isSelected) { - this.props.hideModal(); + if (this.props.isSoundModalVisible) { + this.props.hideModal(); + } else { + this.props.openModalForSound(this.props.sound); + } this.props.deselectSound(); } else { this.props.deselectAllSounds(); // does not work on sounds.selectedSounds @@ -89,6 +91,7 @@ class MapCircleContainer extends React.PureComponent { } render() { + // debugger; if (!isSoundVisible(this.props)) { return null; } @@ -110,10 +113,14 @@ const makeMapStateToProps = (_, ownProps) => { const isSoundSelected = makeIsSoundSelected(soundID); return (state) => { const sound = state.sounds.byID[soundID]; + const selectedSounds = state.sounds.selectedSounds; + const isSoundModalVisible = state.sounds.soundInfoModal.isVisible; const { shouldPlayOnHover } = state.settings; const isSelected = isSoundSelected(state); return { sound, + selectedSounds, + isSoundModalVisible, isThumbnail, shouldPlayOnHover, isSelected, From 92ee1862aca0e671223b2615b2481f237a0e431f Mon Sep 17 00:00:00 2001 From: Eric Lehmann Date: Wed, 14 Mar 2018 15:49:13 +0100 Subject: [PATCH 15/27] Fixes permanent error message in the SoundList Tab --- src/containers/Sounds/SoundListContainer.jsx | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/src/containers/Sounds/SoundListContainer.jsx b/src/containers/Sounds/SoundListContainer.jsx index 7af3df3b..bd15bd60 100644 --- a/src/containers/Sounds/SoundListContainer.jsx +++ b/src/containers/Sounds/SoundListContainer.jsx @@ -13,20 +13,18 @@ const propTypes = { // Q?? class or not?! tab as container? // const space = props => getCurrentSpace(props.spaces.spaces, props.currentSpace); -// TODO: update error message const SoundListContainer = props => (
    - {typeof props.sounds.byID.length !== 'undefined' ? null : -
    No sounds to list, please search first!
    } - {typeof props.space === 'undefined' ? null : -
    - -
    } + {typeof props.space === 'undefined' ? +
    No sounds to list, please search first!
    : +
    + +
    }
    ); From e1c4802ac32fdc7183b2665aa9a715e6e4b2899e Mon Sep 17 00:00:00 2001 From: Eric Lehmann Date: Thu, 15 Mar 2018 14:46:08 +0100 Subject: [PATCH 16/27] Changed selection and playback behaviour to later ctr/cmd + click behaviour --- src/containers/Sounds/MapCircleContainer.jsx | 33 +++++++++++++------- 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/src/containers/Sounds/MapCircleContainer.jsx b/src/containers/Sounds/MapCircleContainer.jsx index f934ae4a..1e847a6d 100644 --- a/src/containers/Sounds/MapCircleContainer.jsx +++ b/src/containers/Sounds/MapCircleContainer.jsx @@ -16,12 +16,13 @@ const propTypes = { playAudio: React.PropTypes.func, stopAudio: React.PropTypes.func, selectSound: React.PropTypes.func, + selectedSounds: React.PropTypes.array, deselectSound: React.PropTypes.func, deselectAllSounds: React.PropTypes.func, toggleHoveringSound: React.PropTypes.func, openModalForSound: React.PropTypes.func, hideModal: React.PropTypes.func, - isSoundModalVisible: React.PropTypes.bool, + soundInfoModal: React.PropTypes.object, }; const isSoundVisible = (props) => { @@ -63,24 +64,33 @@ class MapCircleContainer extends React.PureComponent { } onClick() { - if (this.props.sound.isPlaying) { + // console.log(this.props.selectedSounds); + // console.log('modalID: ' + this.props.soundInfoModal.soundID + 'sel> ' + this.props.isSelected); + // console.log('sound: ' + this.props.sound.id + 'pl> ' + this.props.sound.isPlaying); + // console.log(this.props.soundInfoModal.soundID === this.props.sound.soundID); + // play and stop sound + if (!this.props.sound.isPlaying && this.props.isSelected) { this.props.stopAudio(this.props.sound); } else { this.props.playAudio(this.props.sound); } - if (this.props.isSelected) { - if (this.props.isSoundModalVisible) { + // toggle selecion + this.props.deselectSound(this.props.sound.id); + + // hide modal only if it is the one of the last clicked sound + if (this.props.soundInfoModal.isVisible + && this.props.soundInfoModal.soundID === this.props.sound.id) { this.props.hideModal(); - } else { - this.props.openModalForSound(this.props.sound); } - this.props.deselectSound(); } else { - this.props.deselectAllSounds(); // does not work on sounds.selectedSounds - this.props.openModalForSound(this.props.sound); + // toggle selection this.props.selectSound(this.props.sound.id); + + // open modal if sound is not yet selected + this.props.openModalForSound(this.props.sound); } + console.log(this.props.selectedSounds); } shouldThumbnailUpdate(nextProps) { @@ -91,7 +101,6 @@ class MapCircleContainer extends React.PureComponent { } render() { - // debugger; if (!isSoundVisible(this.props)) { return null; } @@ -114,13 +123,13 @@ const makeMapStateToProps = (_, ownProps) => { return (state) => { const sound = state.sounds.byID[soundID]; const selectedSounds = state.sounds.selectedSounds; - const isSoundModalVisible = state.sounds.soundInfoModal.isVisible; + const soundInfoModal = state.sounds.soundInfoModal; const { shouldPlayOnHover } = state.settings; const isSelected = isSoundSelected(state); return { sound, selectedSounds, - isSoundModalVisible, + soundInfoModal, isThumbnail, shouldPlayOnHover, isSelected, From 7adb642e279373eebe5188ea4360764f923a137a Mon Sep 17 00:00:00 2001 From: Eric Lehmann Date: Thu, 15 Mar 2018 15:19:41 +0100 Subject: [PATCH 17/27] Adds isUserLoggedIn to props of the SoundInfo Modal for showing the buttons --- src/containers/SoundInfo/SoundInfoContainer.jsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/containers/SoundInfo/SoundInfoContainer.jsx b/src/containers/SoundInfo/SoundInfoContainer.jsx index a8ea60bb..dfbc5c8f 100644 --- a/src/containers/SoundInfo/SoundInfoContainer.jsx +++ b/src/containers/SoundInfo/SoundInfoContainer.jsx @@ -33,9 +33,10 @@ const SoundInfoContainer = (props) => { const mapStateToProps = (state) => { const { notesMapped, soundCurrentlyLearnt, isMidiSupported } = state.midi; const { selectedPath } = state.paths; + const { isUserLoggedIn } = state.login; const sound = state.sounds.byID && state.sounds.byID[state.sounds.soundInfoModal.soundID]; return Object.assign({}, state.sounds.soundInfoModal, - { sound, selectedPath, notesMapped, soundCurrentlyLearnt, isMidiSupported }); + { sound, selectedPath, notesMapped, soundCurrentlyLearnt, isMidiSupported, isUserLoggedIn }); }; SoundInfoContainer.propTypes = propTypes; From 4a62fbb89a48ef9b8dfce880a538d3aa309c8ec6 Mon Sep 17 00:00:00 2001 From: Eric Lehmann Date: Thu, 15 Mar 2018 15:20:50 +0100 Subject: [PATCH 18/27] Adds prop type check to is MIDI supported, but it not used anyway... --- src/containers/SoundInfo/SoundInfoContainer.jsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/containers/SoundInfo/SoundInfoContainer.jsx b/src/containers/SoundInfo/SoundInfoContainer.jsx index dfbc5c8f..4a8c65b0 100644 --- a/src/containers/SoundInfo/SoundInfoContainer.jsx +++ b/src/containers/SoundInfo/SoundInfoContainer.jsx @@ -21,6 +21,7 @@ const propTypes = { addSoundToPath: React.PropTypes.func, downloadSound: React.PropTypes.func, bookmarkSound: React.PropTypes.func, + isMidiSupported: React.PropTypes.bool, }; const SoundInfoContainer = (props) => { From 7d82cf613f296f478136431590274ae2991e9cee Mon Sep 17 00:00:00 2001 From: Eric Lehmann Date: Thu, 15 Mar 2018 15:50:02 +0100 Subject: [PATCH 19/27] Implements a download workaround in the sound modal via freesound website, requires user to log in twice --- src/components/Sounds/SoundInfo.jsx | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/components/Sounds/SoundInfo.jsx b/src/components/Sounds/SoundInfo.jsx index c80ee10a..9adb6383 100644 --- a/src/components/Sounds/SoundInfo.jsx +++ b/src/components/Sounds/SoundInfo.jsx @@ -50,6 +50,8 @@ class SoundInfo extends React.Component { }, ''); } + + // TODO: implement direct GET request, this is a shortcut -> downloadSound is now bypassed getFreesoundButtons() { let bookmarkSoundIcon = null; let downloadSoundIcon = null; @@ -64,9 +66,11 @@ class SoundInfo extends React.Component { ); downloadSoundIcon = ( -