Selaa lähdekoodia

Finish Mod List View

StarGazer1258 5 vuotta sitten
vanhempi
commit
2b148a490d

+ 1 - 1
package.json

@@ -2,7 +2,7 @@
   "name": "beatdrop",
   "description": "A desktop app for downloading Beat Saber songs.",
   "author": "Nathaniel Johns (StarGazer1258)",
-  "version": "2.1.9-alpha",
+  "version": "2.2.0-alpha",
   "private": false,
   "license": "CC-BY-NC-SA-4.0",
   "repository": {

+ 4 - 4
src/actions/modActions.js

@@ -160,7 +160,7 @@ export const fetchLocalMods = () => (dispatch, getState) => {
     type: SET_LOADING,
     payload: true
   })
-  fetch(`https://beatmods.com/api/v1/mod?status=approved`)
+  fetch(`https://beatmods.com/api/v1/mod?status=approved&status=inactive`)
     .then(res => res.json())
     .then(beatModsResponse => {
       let installedMods = getState().mods.installedMods
@@ -202,7 +202,7 @@ export const fetchActivatedMods = () => (dispatch, getState) => {
     type: SET_LOADING,
     payload: true
   })
-  fetch(`https://beatmods.com/api/v1/mod?status=approved`)
+  fetch(`https://beatmods.com/api/v1/mod?status=approved&status=inactive`)
     .then(res => res.json())
     .then(beatModsResponse => {
       let activatedMods = getState().mods.installedMods.filter(mod => mod.active === true)
@@ -240,7 +240,7 @@ export const loadModDetails = modId => dispatch => {
     type: SET_LOADING,
     payload: true
   })
-  fetch(`https://beatmods.com/api/v1/mod?status=approved`)
+  fetch(`https://beatmods.com/api/v1/mod?status=approved&status=inactive`)
     .then(res => res.json())
     .then(beatModsResponse => {
       dispatch({
@@ -283,7 +283,7 @@ export const installMod = (modName, version, dependencyOf = '') => (dispatch, ge
     return
   }
   console.log(`Fetching ${modName}@${version} from BeatMods...`)
-  fetch(`https://beatmods.com/api/v1/mod?status=approved&name=${encodeURIComponent(modName)}&version=${version}`)
+  fetch(`https://beatmods.com/api/v1/mod?status=approved&status=inactive&name=${encodeURIComponent(modName)}&version=${version}`)
     .then(res => res.json())
     .then(beatModsResponse => {
       console.log(`Got the BeatMods response for ${modName}@${version}`)

+ 1 - 1
src/actions/types.js

@@ -56,7 +56,7 @@ export const SET_RESOURCE_URL           = 'SET_RESOURCE_URL'
 
 // View
 export const SET_VIEW                   = 'SET_VIEW'
-export const SET_SONG_VIEW              = 'SET_SONG_VIEW'
+export const SET_SUB_VIEW              = 'SET_SONG_VIEW'
 
 // Window
 export const RESIZE_WINDOW              = 'RESIZE_WINDOW'

+ 3 - 3
src/actions/viewActions.js

@@ -1,4 +1,4 @@
-import { SET_VIEW, SET_SONG_VIEW, SET_SCROLLTOP } from './types'
+import { SET_VIEW, SET_SUB_VIEW, SET_SCROLLTOP } from './types'
 
 export const setView = view => dispatch => {
   dispatch({
@@ -7,9 +7,9 @@ export const setView = view => dispatch => {
   })
 }
 
-export const setSongView = view => dispatch => {
+export const setSubView = view => dispatch => {
   dispatch({
-    type: SET_SONG_VIEW,
+    type: SET_SUB_VIEW,
     payload: view
   })
   dispatch({

Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 1 - 0
src/assets/dark/loading.svg


+ 1 - 1
src/components/Checkbox.js

@@ -3,7 +3,7 @@ import '../css/Checkbox.scss'
 
 class Checkbox extends Component {
     render() {
-        return <div className={ `checkbox${ this.props.checked ? ' checked' : '' }${ this.props.indeterminate ? ' indeterminate' : '' }${ this.props.disabled ? ' disabled' : '' }` } onClick={ this.props.onChange }></div>
+        return <div className={ `checkbox${ this.props.checked ? ' checked' : '' }${ this.props.indeterminate ? ' indeterminate' : '' }${ this.props.disabled ? ' disabled' : '' }` } onClick={ (e) => { e.preventDefault(); e.stopPropagation(); if(!this.props.disabled) this.props.onChange() } }></div>
     }
 }
 

+ 49 - 10
src/components/ModsListView.js

@@ -1,11 +1,16 @@
-import React, { Component, Fragment } from 'react'
+import React, { Component } from 'react'
 import '../css/ModsListView.scss'
 
 import Checkbox from './Checkbox'
 
 import { installMod, uninstallMod, loadModDetails } from '../actions/modActions'
 
+import { BEATMODS, LIBRARY } from '../constants/resources';
+
+import Loader from '../assets/loading-dots2.png'
+
 import { connect } from 'react-redux'
+import SortBar from './SortBar';
 
 class ModsListView extends Component {
   constructor(props) {
@@ -17,16 +22,41 @@ class ModsListView extends Component {
       sortBy: 'name',
       sortDirection: 0
     }
+
+    this.SubCategory = this.SubCategory.bind(this)
+  }
+
+  SubCategory() {
+    switch(this.props.resource) {
+      case BEATMODS.NEW_MODS:
+        return <h1>Approved Mods</h1>
+      case BEATMODS.RECOMMENDED_MODS:
+        return <h1>Recommended Mods</h1>
+      case BEATMODS.MOD_CATEGORIES:
+        return <h1>{ this.state.category }</h1>
+      case BEATMODS.MOD_CATEGORY_SELECT:
+        return <h1>Categories</h1>
+      case LIBRARY.MODS.ALL:
+        return <h1>Library Mods</h1>
+      case LIBRARY.MODS.ACTIVATED:
+        return <h1>Activated Mods</h1>
+      default:
+        return null
+    }
   }
 
   render() {
       return (
         <div id="mod-list">
-          <h1>Mods</h1>
+          <SortBar />
+          <this.SubCategory />
+          {this.props.loading ?
+            <img style={ { width: '200px', marginLeft: 'calc(50% - 100px)' } } src={ Loader } alt="Loading..."/>
+          :
           <table>
             <thead>
               <tr>
-                <th><span></span></th>
+                <th><span>&nbsp;</span></th>
                 <th onClick={ () => { this.setState({ sortBy: 'name', sortDirection: this.state.sortBy === 'name' ? !this.state.sortDirection : 0 }) } }><span>Mod Name{ this.state.sortBy === 'name' ? this.state.sortDirection ? '▼' : '▲' : null }</span></th>
                 <th width={ 80 } onClick={ () => { this.setState({ sortBy: 'version', sortDirection: this.state.sortBy === 'version' ? !this.state.sortDirection : 0 }) } }><span>Version{ this.state.sortBy === 'version' ? this.state.sortDirection ? '▼' : '▲' : null }</span></th>
                 <th onClick={ () => { this.setState({ sortBy: 'author', sortDirection: this.state.sortBy === 'author' ? !this.state.sortDirection : 0 }) } }><span>Author{ this.state.sortBy === 'author' ? this.state.sortDirection ? '▼' : '▲' : null }</span></th>
@@ -47,6 +77,7 @@ class ModsListView extends Component {
                       case 'version':
                         if(a.version < b.version) return this.state.sortDirection * 2 - 1
                         if(a.version > b.version) return -(this.state.sortDirection * 2 - 1)
+                        return 0
                       case 'author':
                         if(a.author.username < b.author.username) return this.state.sortDirection * 2 - 1
                         if(a.author.username > b.author.username) return -(this.state.sortDirection * 2 - 1)
@@ -63,13 +94,19 @@ class ModsListView extends Component {
                         return 0
                     }
                   })
-                  .map((mod, i) => {
+                  .map(mod => {
                     return (
-                      <tr key={ `${mod.name}@${mod.version}${this.props.mods.pendingInstall.some(m => m.name === mod.name) ? '.installed' : ''}` }>
-                        <td width={ 20 }>
+                      <tr key={ `${mod.name}@${mod.version}${this.props.mods.pendingInstall.some(m => m.name === mod.name) ? '.installed' : ''}` }  onClick={ () => { this.props.loadModDetails(mod._id) } }>
+                        <td
+                          width={ 20 }
+                          title={
+                            this.props.mods.installedMods.some(m => m.name === mod.name) ?
+                              this.props.mods.installedMods.filter(m => m.name === mod.name)[0].dependencyOf.some(dependent => this.props.mods.installedMods.some(installedMod => installedMod.name === dependent)) ? `${mod.name} cannot be uninstalled: mod is a dependency of another installed mod.` : `Uninstall ${mod.name}`
+                            : `Install ${mod.name}` }
+                        >
                           {
                             this.props.mods.pendingInstall.some(m => m === mod.name) ?
-                              <img src={ require('../assets/loading.svg') } alt="Installing..." style={ { height: "22px" } }/>
+                              <div className="installing-mod" />
                             :
                           <Checkbox
                             checked={ this.props.mods.installedMods.some(m => m.name === mod.name) }
@@ -88,7 +125,7 @@ class ModsListView extends Component {
                           />
                         }
                         </td>
-                        <td onClick={ () => { this.props.loadModDetails(mod._id) } }>{ mod.name }</td>
+                        <td>{ mod.name }</td>
                         <td>{ mod.version }</td>
                         <td>{ mod.author.username }</td>
                         <td>{ mod.category }</td>
@@ -98,7 +135,7 @@ class ModsListView extends Component {
                   })
               }
             </tbody>
-          </table>
+          </table>}
         </div>
         
       )
@@ -106,7 +143,9 @@ class ModsListView extends Component {
 }
 
 const mapStateToProps = state => ({
-  mods: state.mods
+  mods: state.mods,
+  resource: state.resource,
+  loading: state.loading
 })
 
 export default connect(mapStateToProps, { installMod, uninstallMod, loadModDetails })(ModsListView)

+ 32 - 64
src/components/ModsView.js

@@ -11,76 +11,25 @@ import { ContextMenuTrigger, MenuItem, ContextMenu } from 'react-contextmenu';
 import { makeRenderKey } from '../utilities'
 import LibraryIndicator from './LibraryIndicator';
 import DeactivatedIndicator from './DeactivatedIndicator';
+import SortBar from './SortBar';
 
 const { clipboard, shell } = window.require('electron')
 
-const categories = [
-  {
-    class: 'core',
-    name: 'Core'
-  },
-  {
-    class: 'cosmetic',
-    name: 'Cosmetic'
-  },
-  {
-    class: 'training',
-    name: 'Practice / Training'
-  },
-  
-  {
-    class: 'gameplay',
-    name: 'Gameplay'
-  },
-  {
-    class: 'stream',
-    name: 'Streaming Tools'
-  },
-  {
-    class: 'library',
-    name: 'Libraries'
-  },
-  {
-    class: 'text',
-    name: 'UI Enhancements'
-  }
-  /*{
-    class: 'lighting',
-    name: 'Lighting Changes'
-  },
-  {
-    class: 'tweak',
-    name: 'Tweaks / Tools'
-  },
-  {
-    class: 'multiplayer',
-    name: 'Multiplayer'
-  },
-  {
-    class: 'text',
-    name: 'Text Changes'
-  },
-  {
-    class: 'other',
-    name: 'Other'
-  }*/
-]
-
-
 class ModsView extends Component {
-
   Catergories(props) {
     return (
-      <div className="categories-list">
-        {categories.map((category) => {
-          return (
-            <div className="category-tile" onClick={ () => { props.fetchModCategory(category.name.toLowerCase()); this.setState({ category: category.name }) } }>
-              <div className={ `category-image ${category.class}` }></div>
-              <div className="category-name">{category.name}</div>
-            </div>
-          )
-        })}
-      </div>
+      <ul className="categories-list">
+        { this.state.categories ? 
+          this.state.categories.map((category) => {
+            return (
+              <li className="category" onClick={ () => { props.fetchModCategory(category.toLowerCase()); this.setState({ category }) } }>
+                <div className="category-name">{category}</div>
+              </li>
+            )
+          })
+        :
+         null }
+      </ul>
     )
   }
 
@@ -101,6 +50,22 @@ class ModsView extends Component {
     }
   }
 
+  componentDidMount() {
+    let categories = []
+    let prevCat = ''
+    fetch('https://beatmods.com/api/v1/mod?status=approved')
+      .then(res => res.json())
+      .then(beatModsResponse => {
+        for(let i = 0; i < beatModsResponse.length; i++) {
+          if(beatModsResponse[i].category !== prevCat) {
+            prevCat = beatModsResponse[i].category
+            categories.push(beatModsResponse[i].category)
+          }
+        }
+        this.setState({ categories })
+      })
+  }
+
   constructor(props) {
     super(props)
 
@@ -136,6 +101,8 @@ class ModsView extends Component {
       )
     } else {
       return (
+        <>
+        <SortBar />
         <div id="mod-marketplace">
           <h1>Mods</h1>
           <this.SubCategory sub={ this.props.resource } category={ this.state.category } />
@@ -200,6 +167,7 @@ class ModsView extends Component {
               </div> : <this.Catergories fetchModCategory={ this.props.fetchModCategory } />
             }
         </div>
+        </>
       )
     }
   }

+ 3 - 10
src/components/ReleaseNotesModal.js

@@ -9,8 +9,6 @@ import { setLatestReleaseNotes } from '../actions/settingsActions'
 
 const { ipcRenderer } = window.require('electron')
 
-const { shell } = window.require('electron')
-
 class ReleaseNotesModal extends Component {
 
   componentDidMount() {
@@ -27,18 +25,13 @@ class ReleaseNotesModal extends Component {
             <h2 style={ { color: 'lightgreen' } }>What's new?</h2>
             <hr style={ { borderColor: 'lightgreen' } } />
             <ul>
-              <li>An extra special thanks to my first Patreon Supporter: <b>Shane R. Monroe</b>! Your name will now forever be engraved in the credits of BeatDrop!</li>
-              <li>Want your name to be there too? <a href="https://www.patreon.com/bePatron?u=18487054" onClick={ (e) => { e.preventDefault(); e.stopPropagation(); shell.openExternal(e.target.href) } }>Become a Patron</a> at the Wave tier and above!</li>
-              <li>Added <b>labels for bottom-left buttons.</b></li>
-              <li>Better <b>animations for sidebar.</b></li>
+              <li>Just fixes for now :)</li>
             </ul>
             <h2 style={ { color: 'salmon' } }>What's fixed?</h2>
             <hr style={ { borderColor: 'salmon' } } />
             <ul>
-              <li>Fixed <a href="https://github.com/StarGazer1258/BeatDrop/issues/3" onClick={ (e) => { e.preventDefault(); e.stopPropagation(); shell.openExternal(e.target.href) } }>#3 - App crashes when local mods are loaded</a></li>
-              <li>Fixed <a href="https://github.com/StarGazer1258/BeatDrop/issues/4" onClick={ (e) => { e.preventDefault(); e.stopPropagation(); shell.openExternal(e.target.href) } }>#4 - Right-Click context menu shows options for multiple other songs, but clicked one</a></li>
-              <li>Fixed <a href="https://github.com/StarGazer1258/BeatDrop/issues/5" onClick={ (e) => { e.preventDefault(); e.stopPropagation(); shell.openExternal(e.target.href) } }>#5 - App installs incorrect version of mod</a></li>
-              <li>Fixed <a href="https://github.com/StarGazer1258/BeatDrop/issues/7" onClick={ (e) => { e.preventDefault(); e.stopPropagation(); shell.openExternal(e.target.href) } }>#7 - 'View on BeastSaber' opens wrong URL</a></li>
+              <li>Fixed a big oof where <b>non-latest dependencies would not be installed.</b></li>
+              <li>Fixed <b>spelling of names in Patreon credits.</b></li>
             </ul>
             <br />
             <Button type="primary" onClick={ () => { this.props.setLatestReleaseNotes(require('../../package.json').version) } }>Awesome!</Button>

+ 1 - 1
src/components/SettingsView.js

@@ -117,7 +117,7 @@ class SettingsView extends Component {
           <li>
           <b>Wave Tier</b>
           <ul>
-            <li>Shane R. Munroe</li>
+            <li>Shane R. Monroe</li>
           </ul>
           </li>
         </ul>

Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 2 - 1
src/components/SideBar.js


+ 1 - 1
src/components/SongList.js

@@ -84,7 +84,7 @@ class SongList extends Component {
                 tag: '.ratings-loaded'
               },
               {
-                boolean: this.props.view.songView === 'compact-list',
+                boolean: this.props.view.subView === 'compact-list',
                 tag: '.compact'
               }
             ]

+ 1 - 1
src/components/SongListItem.js

@@ -97,7 +97,7 @@ class SongListItem extends Component {
       )
     } else {
       return (
-        <li className={ `song-list-item${this.props.view.songView === 'compact-list' ? ' compact' : ''}` } onClick={ () => { this.props.setScrollTop(document.getElementById('song-list').scrollTop); this.props.loadDetails(this.props.file || this.props.songKey) } }>
+        <li className={ `song-list-item${this.props.view.subView === 'compact-list' ? ' compact' : ''}` } onClick={ () => { this.props.setScrollTop(document.getElementById('song-list').scrollTop); this.props.loadDetails(this.props.file || this.props.songKey) } }>
           <img className="cover-image" src={ this.props.imageSource } alt={ this.props.songKey } />
           {(!!this.props.file || this.props.downloadedSongs.some(dsong => dsong.hash === this.props.hash)) && this.props.view.songView !== COMPACT_LIST ? <LibraryIndicator /> : null}
           <div className="song-details">

+ 6 - 6
src/components/SortBar.js

@@ -5,7 +5,7 @@ import '../css/SortBar.scss'
 import TabGroup from './TabGroup';
 import Tab from './Tab';
 
-import { setSongView } from '../actions/viewActions'
+import { setSubView } from '../actions/viewActions'
 
 import * as VIEWS from '../views'
  
@@ -14,22 +14,22 @@ class SortBar extends Component {
     if(this.props.hidden) return null
     return (
       <div className='sort-bar'>
-        { this.props.view === VIEWS.SONG_LIST && <TabGroup label="View:"><Tab active={ this.props.songView === 'list' } onClick={ () => {this.props.setSongView('list')} }>List</Tab><Tab active={ this.props.songView === 'compact-list' } onClick={ () => {this.props.setSongView('compact-list')} }>Compact List</Tab><Tab active={ this.props.songView === 'grid' } onClick={ () => {this.props.setSongView('grid')} }>Grid</Tab></TabGroup> }
-        { this.props.view === VIEWS.MODS_VIEW && <TabGroup label="View:"><Tab active={ this.props.songView === 'basic' } onClick={ () => {this.props.setSongView('basic')} }>Basic</Tab><Tab active={ this.props.songView === 'advanced' } onClick={ () => {this.props.setSongView('advanced')} }>Advanced</Tab></TabGroup> }
+        { this.props.view === VIEWS.SONG_LIST && <TabGroup label="View:"><Tab active={ this.props.subView === 'list' } onClick={ () => {this.props.setSubView('list')} }>List</Tab><Tab active={ this.props.subView === 'compact-list' } onClick={ () => {this.props.setSubView('compact-list')} }>Compact List</Tab><Tab active={ this.props.subView === 'grid' } onClick={ () => {this.props.setSubView('grid')} }>Grid</Tab></TabGroup> }
+        { this.props.view === VIEWS.MODS_VIEW && <TabGroup label="View:"><Tab active={ this.props.subView === 'list' } onClick={ () => {this.props.setSubView('list')} }>List</Tab><Tab active={ this.props.subView === 'tiles' } onClick={ () => {this.props.setSubView('tiles')} }>Tiles</Tab></TabGroup> }
       </div>
     )
   }
 }
 
 SortBar.propTypes = {
-  setSongView: PropTypes.func.isRequired
+  setsubView: PropTypes.func.isRequired
 }
 
 let mapStateToProps = (state) => ({
   view: state.view.view,
-  songView: state.view.songView
+  subView: state.view.subView
 })
 
-export default connect(mapStateToProps, { setSongView })(SortBar)
+export default connect(mapStateToProps, { setSubView })(SortBar)
 
 //<TabGroup label="Sort Order:"><Tab active={true}>Ascending</Tab><Tab>Descending</Tab></TabGroup>

+ 8 - 8
src/components/ViewSwitcher.js

@@ -21,7 +21,7 @@ import ModDetails from './ModDetails'
 import ModsListView from './ModsListView';
 
 function Songs(props) {
-  switch(props.songView) {
+  switch(props.subView) {
     case 'list':
       return <SongList />
     case 'compact-list':
@@ -40,11 +40,11 @@ function MainView(props) {
     case VIEWS.DONATE:
       return <DonateView />
     case VIEWS.SONG_LIST:
-      return <Songs songView={ props.songView } />
+      return <Songs subView={ props.subView } />
     case VIEWS.PLAYLIST_LIST:
       return <PlaylistView />
     case VIEWS.MODS_VIEW:
-      return <ModsListView />
+      return props.subView === 'list' ? <ModsListView /> : <ModsView />
     case VIEWS.SETTINGS:
       return <SettingsView />
     case VIEWS.SEARCH:
@@ -56,7 +56,7 @@ function MainView(props) {
     case VIEWS.MOD_DETAILS:
       return <ModDetails />
     default:
-      return <Songs songView={ props.songView } />
+      return <Songs subView={ props.subView } />
   }
 }
 
@@ -77,8 +77,8 @@ class ViewSwitcher extends Component {
       <div id="view-switcher" className={ `theme-${this.props.settings.theme}` }>
         <SideBar />
         <div className={ `view-right ${this.props.sidebarOpen ? '' : 'sidebar-collapsed'}` }>
-          { [VIEWS.SONG_LIST, VIEWS.MODS_VIEW].some(view => this.props.view === view) && <SortBar /> }
-          <MainView view={ this.props.view } songView={ this.props.songView } />
+          { [VIEWS.SONG_LIST].some(view => this.props.view === view) && <SortBar /> }
+          <MainView view={ this.props.view } subView={ this.props.subView } />
           <Warnings warnings={ this.props.warnings } />
         </div>
       </div>
@@ -88,7 +88,7 @@ class ViewSwitcher extends Component {
 
 ViewSwitcher.propTypes = {
   view: PropTypes.string.isRequired,
-  songView: PropTypes.string.isRequired,
+  subView: PropTypes.string.isRequired,
   settings: PropTypes.object.isRequired,
   details: PropTypes.object.isRequired,
   warnings: PropTypes.array,
@@ -97,7 +97,7 @@ ViewSwitcher.propTypes = {
 
 const mapStateToProps = state => ({
   view: state.view.view,
-  songView: state.view.songView,
+  subView: state.view.subView,
   settings: state.settings,
   details: state.details,
   warnings: state.warnings,

+ 21 - 23
src/css/ModDetails.scss

@@ -122,40 +122,38 @@
   }
 }
 
-.theme-dark {
-  #mod-details {
-    .action-buttons .action-button {
-      background-color: rgba($theme-dark-accent-color, 0.8) !important;
+.theme-dark #mod-details {
+  .action-buttons .action-button {
+    background-color: rgba($theme-dark-accent-color, 0.8) !important;
 
-      &:hover {
-        background: $theme-dark-accent-color !important;
-      }
+    &:hover {
+      background: $theme-dark-accent-color !important;
+    }
 
-      &.install-button {
-        background: rgba($theme-dark-accent-color, 0.3) !important;
-        border: 3px solid rgba($theme-dark-accent-color, 0.8) !important;
-        
-        span:first-child {
-          background: rgba($theme-dark-accent-color, 0.8) !important;
-        }
+    &.install-button {
+      background: rgba($theme-dark-accent-color, 0.3) !important;
+      border: 3px solid rgba($theme-dark-accent-color, 0.8) !important;
+      
+      span:first-child {
+        background: rgba($theme-dark-accent-color, 0.8) !important;
+      }
 
-        &:hover {
-          border: 3px solid $theme-dark-accent-color !important;
+      &:hover {
+        border: 3px solid $theme-dark-accent-color !important;
 
-          span:first-child {
-            background: $theme-dark-accent-color !important;
-          }
+        span:first-child {
+          background: $theme-dark-accent-color !important;
         }
       }
     }
+  }
 
-    .badge {
-      background-color: $theme-dark-accent-color !important;
-    }
+  .badge {
+    background-color: $theme-dark-accent-color !important;
   }
 }
 
-.theme-hc {
+.theme-hc #mod-details {
   .action-buttons .action-button {
     background-color: rgba($theme-hc-accent-color, 0.8) !important;
 

+ 28 - 10
src/css/ModsListView.scss

@@ -2,28 +2,35 @@
 
 #mod-list {
   height: 100%;
-  padding: 0 20px;
 
   h1 {
     font-size: 28pt;
-    margin-top: 15px;
-    margin-left: 10px;
+    margin: 10px 15px;
+    margin-bottom: 15px;
   }
 
   table {
     display: block;
     width: 100%;
-    height: calc(100% - 112px);
+    height: calc(100% - 100px);
     overflow-y: scroll;
+    overflow-x: hidden;
   }
 
   th, td, span {
     text-align: left;
+    overflow: hidden;
+    white-space: nowrap;
+    text-overflow: ellipsis;
+  }
+  
+  tr {
+    cursor: pointer;
   }
 
   th span {
     position: absolute;
-    top: 60px;
+    top: 54px;
     width: 100%;
     display: block;
     color: $beatdrop-blue;
@@ -36,13 +43,24 @@
     font-weight: 700;
   }
 
-  tbody {
-    position: relative;
-    top: 50px;
-
-    td {
+  tbody td {
       padding: 5px;
     }
+
+  .installing-mod {
+    width: 25px;
+    height: 25px;
+    background: url('../assets/loading.svg');
+    background-position: center;
+    background-repeat: no-repeat;
+  }
+}
+
+.theme-dark, .theme-hc {
+  #mod-list .installing-mod {
+    background: url('../assets/dark/loading.svg');
+    background-position: center;
+    background-repeat: no-repeat;
   }
 }
 

+ 17 - 2
src/css/ModsView.scss

@@ -13,11 +13,26 @@
   }
 
   .categories-list {
+    list-style-type: none;
     display: grid;
     grid-gap: 10px;
     grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
     margin-bottom: 30px;
 
+    .category {
+      border: 1px solid $theme-light-line-color;
+      border-radius: 5px;
+      cursor: pointer;
+      font-size: 16pt;
+      padding: 10px;
+      box-shadow: 2px 2px 3px 0px rgba(0,0,0,0.1);
+      transition: transform .2s;
+      
+      &:hover {
+        transform: scale(1.02);
+      }
+    }
+
     .category-tile {
       min-width: 200px;
       border: 1px solid $theme-light-line-color;
@@ -213,7 +228,7 @@
 
 .theme-dark {
   #mod-marketplace {
-    .mod-marketplace-tile, .category-tile {
+    .mod-marketplace-tile, .category {
       background: rgba(255, 255, 255, 0.1);
     }
   }
@@ -221,7 +236,7 @@
 
 .theme-hc {
   #mod-marketplace {
-    .mod-marketplace-tile, .category-tile {
+    .mod-marketplace-tile, .category {
       border: 1px solid $theme-hc-line-color;
     }
   }

+ 5 - 5
src/reducers/viewReducer.js

@@ -1,10 +1,10 @@
-import  { SET_VIEW, SET_SONG_VIEW } from '../actions/types'
+import  { SET_VIEW, SET_SUB_VIEW } from '../actions/types'
 import { WELCOME, SONG_LIST } from '../views'
 
 const initialState = {
   previousView: SONG_LIST,
   view: WELCOME,
-  songView: 'list'
+  subView: 'list'
 }
 
 export default function(state = initialState, action) {
@@ -13,12 +13,12 @@ export default function(state = initialState, action) {
       return {
         ...state,
         previousView: state.view,
-        view: action.payload
+        view: action.payload,
       }
-    case SET_SONG_VIEW:
+    case SET_SUB_VIEW:
       return {
         ...state,
-        songView: action.payload
+        subView: action.payload
       }
     default:
       return state