Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Sign in / Register
Toggle navigation
D
Desktop Client
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Analytics
Analytics
CI / CD
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
SeeBeyond
Desktop Client
Commits
1bca3c81
Commit
1bca3c81
authored
Apr 23, 2020
by
SpinShare
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
added library
parent
e501f626
Changes
14
Hide whitespace changes
Inline
Side-by-side
Showing
14 changed files
with
706 additions
and
146 deletions
+706
-146
public/index.html
public/index.html
+1
-1
src/App.vue
src/App.vue
+9
-61
src/assets/img/defaultAvatar.jpg
src/assets/img/defaultAvatar.jpg
+0
-0
src/components/Song/SongInstallItem.vue
src/components/Song/SongInstallItem.vue
+103
-0
src/components/Song/SongLocalItem.vue
src/components/Song/SongLocalItem.vue
+127
-0
src/components/Song/SongRow.vue
src/components/Song/SongRow.vue
+54
-53
src/components/User/UserItem.vue
src/components/User/UserItem.vue
+104
-0
src/components/User/UserRow.vue
src/components/User/UserRow.vue
+56
-0
src/modules/module.api.js
src/modules/module.api.js
+16
-0
src/modules/module.srxd.js
src/modules/module.srxd.js
+11
-6
src/modules/module.usersettings.js
src/modules/module.usersettings.js
+55
-0
src/views/Library.vue
src/views/Library.vue
+83
-1
src/views/Search.vue
src/views/Search.vue
+80
-20
src/views/Startup.vue
src/views/Startup.vue
+7
-4
No files found.
public/index.html
View file @
1bca3c81
...
@@ -5,7 +5,7 @@
...
@@ -5,7 +5,7 @@
<meta
http-equiv=
"X-UA-Compatible"
content=
"IE=edge"
>
<meta
http-equiv=
"X-UA-Compatible"
content=
"IE=edge"
>
<meta
name=
"viewport"
content=
"width=device-width, initial-scale=1.0"
/>
<meta
name=
"viewport"
content=
"width=device-width, initial-scale=1.0"
/>
<link
rel=
"icon"
href=
"<%= BASE_URL %>favicon.ico"
>
<link
rel=
"icon"
href=
"<%= BASE_URL %>favicon.ico"
>
<title>
<
%=
htmlWebpackPlugin
.
options
.
title
%
>
</title>
<title>
SpinSha.re
</title>
<!-- Styles -->
<!-- Styles -->
<link
rel=
"stylesheet"
href=
"https://fonts.googleapis.com/css2?family=Open+Sans:wght@400;700&display=swap"
/>
<link
rel=
"stylesheet"
href=
"https://fonts.googleapis.com/css2?family=Open+Sans:wght@400;700&display=swap"
/>
...
...
src/App.vue
View file @
1bca3c81
...
@@ -105,71 +105,19 @@
...
@@ -105,71 +105,19 @@
&:focus {
&:focus {
outline: 0;
outline: 0;
}
}
}
.user-row {
display: grid;
grid-template-rows: auto 1fr;
grid-gap: 5px;
& .user-header {
display: grid;
grid-template-columns: 1fr auto;
& .row-title {
letter-spacing: 0.25em;
font-size: 14px;
font-weight: bold;
text-transform: uppercase;
&.row-title-noactions {
margin: 10px 0px;
}
}
}
& .user-list {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
grid-gap: 15px;
}
}
.user-item {
background: #383c3f;
transition: 0.2s ease-in-out transform, 0.2s ease-in-out box-shadow;
overflow: hidden;
border-radius: 6px;
display: grid;
padding: 10px;
grid-gap: 15px;
grid-template-columns: 32px 1fr;
& .user-avatar {
background: rgba(255,255,255,0.1);
background-size: cover;
background-position: center;
width: 32px;
height: 32px;
border-radius: 32px;
}
& .user-metadata {
&.button-label {
display: flex;
background: transparent;
align-items: center;
& .user-name {
&:hover {
font-weight: bold;
background: rgba(255, 255, 255, 0.2);
overflow: hidden;
color: #fff;
white-space: nowrap;
}
}
& .user-badge {
&:active {
font-size: 18px;
background: #fff;
margin-left: 10px;
color: #222;
cursor: pointer;
}
}
}
}
&:hover {
transform: scale(1.1);
cursor: pointer;
box-shadow: 0px 4px 20px 5px rgba(0, 0, 0, 0.4);
}
}
}
</
style
>
</
style
>
src/assets/img/defaultAvatar.jpg
0 → 100644
View file @
1bca3c81
51.5 KB
src/components/Song/SongInstallItem.vue
0 → 100644
View file @
1bca3c81
<
template
>
<div
class=
"song-item"
v-on:click=
"install()"
>
<div
class=
"song-cover"
>
<div
class=
"song-icon"
><i
class=
"mdi mdi-folder-music"
></i></div>
</div>
<div
class=
"song-metadata"
>
<div
class=
"song-title"
>
Install
</div>
<div
class=
"song-artist"
>
Install a local .zip
</div>
</div>
</div>
</
template
>
<
script
>
import
{
remote
}
from
'
electron
'
;
const
{
clipboard
}
=
remote
;
export
default
{
name
:
'
SongInstallItem
'
,
data
:
function
()
{
return
{
}
},
mounted
:
function
()
{
},
methods
:
{
install
:
function
(
e
)
{
}
}
}
</
script
>
<
style
scoped
lang=
"less"
>
.song-item {
background: rgba(255,255,255,0.1);
transition: 0.2s ease-in-out transform, 0.2s ease-in-out box-shadow;
overflow: hidden;
border-radius: 6px;
& .song-cover {
background: rgba(255,255,255,0.1);
background-size: cover;
width: 100%;
padding-top: 100%;
position: relative;
background-position: center;
& .song-icon {
position: absolute;
top: 0px;
bottom: 0px;
left: 0px;
right: 0px;
display: flex;
justify-content: center;
align-items: center;
font-size: 32px;
}
}
& .song-metadata {
padding: 15px;
& .song-title {
font-weight: bold;
overflow: hidden;
white-space: nowrap;
}
& .song-artist {
margin-top: 5px;
opacity: 0.6;
overflow: hidden;
white-space: nowrap;
}
& .song-difficulties {
margin-top: 10px;
height: 20px;
display: flex;
& img {
height: 18px;
margin-right: 10px;
opacity: 0.3;
&.active {
opacity: 1;
}
}
}
}
&:hover {
transform: scale(1.1);
cursor: pointer;
box-shadow: 0px 4px 20px 5px rgba(0, 0, 0, 0.4);
& .song-cover {
& .song-charter {
opacity: 1;
}
}
}
}
</
style
>
src/components/Song/SongLocalItem.vue
0 → 100644
View file @
1bca3c81
<
template
>
<div
:class=
"'song-item-local ' + (isSpinShare ? '' : 'song-item-onlylocal')"
v-on:contextmenu=
"showContextMenu($event)"
>
<div
class=
"song-cover"
:style=
"'background-image: url(' + cover + '), url(' + require('@/assets/img/defaultAlbumArt.jpg') + ');'"
>
<div
class=
"song-charter-info"
>
<div
class=
"song-charter"
><i
class=
"mdi mdi-account-circle"
></i><span>
{{
detail
.
charter
?
detail
.
charter
:
"
Unknown
"
}}
</span></div>
</div>
</div>
<div
class=
"song-metadata"
>
<div
class=
"song-title"
>
{{
detail
.
title
?
detail
.
title
:
"
Untitled
"
}}
</div>
<div
class=
"song-artist"
>
{{
detail
.
artist
?
detail
.
artist
:
"
Unknown
"
}}
</div>
</div>
</div>
</
template
>
<
script
>
import
{
remote
}
from
'
electron
'
;
const
{
clipboard
}
=
remote
;
export
default
{
name
:
'
SongLocalItem
'
,
props
:
[
'
id
'
,
'
detail
'
,
'
cover
'
,
'
isSpinShare
'
],
data
:
function
()
{
return
{
isContextMenuActive
:
false
}
},
mounted
:
function
()
{
},
methods
:
{
showContextMenu
:
function
(
e
)
{
this
.
$root
.
$emit
(
'
showContextMenu
'
,
{
x
:
e
.
pageX
,
y
:
e
.
pageY
,
items
:
[
{
icon
:
"
delete
"
,
title
:
"
Delete
"
,
method
:
function
()
{
alert
(
'
TODO
'
)
}.
bind
(
this
)
}
]});
}
}
}
</
script
>
<
style
scoped
lang=
"less"
>
.song-item-local {
background: rgba(255,255,255,0.1);
transition: 0.2s ease-in-out transform, 0.2s ease-in-out box-shadow;
overflow: hidden;
border-radius: 6px;
& .song-cover {
background: rgba(255,255,255,0.1);
background-size: cover;
width: 100%;
padding-top: 100%;
position: relative;
background-position: center;
& .song-charter {
position: absolute;
top: 0px;
left: 0px;
right: 0px;
bottom: 0px;
background: linear-gradient(180deg, rgba(0,0,0,0.2), rgba(0,0,0,0.8));
opacity: 0;
padding: 15px;
overflow: hidden;
display: grid;
transition: 0.2s ease-in-out opacity;
grid-template-columns: auto 1fr;
grid-gap: 10px;
align-items: flex-end;
& .song-charter-info {
display: grid;
align-items: center;
& .mdi {
font-size: 18px;
}
& span {
font-size: 12px;
color: transparent;
transition: 0.2s ease-in-out color;
overflow: hidden;
white-space: nowrap;
}
}
}
}
& .song-metadata {
padding: 15px;
& .song-title {
font-weight: bold;
overflow: hidden;
white-space: nowrap;
}
& .song-artist {
margin-top: 5px;
opacity: 0.6;
overflow: hidden;
white-space: nowrap;
}
}
&:not(.song-item-onlylocal):hover {
transform: scale(1.1);
cursor: pointer;
box-shadow: 0px 4px 20px 5px rgba(0, 0, 0, 0.4);
& .song-cover {
& .song-charter {
opacity: 1;
}
}
}
&.song-item-onlylocal {
opacity: 0.6;
}
}
</
style
>
src/components/Song/SongRow.vue
View file @
1bca3c81
<
template
>
<
template
>
<div
class=
"song-row song-row-new"
>
<div
class=
"song-row song-row-new"
>
<div
class=
"song-header"
>
<div
class=
"song-header"
>
<div
class=
"row-title
"
>
{{
title
}}
</div>
<div
:class=
"'row-title ' + (noactions ? 'row-title-noactions' : '')
"
>
{{
title
}}
</div>
<div
class=
"row-controls"
>
<div
class=
"row-controls"
v-if=
"!noactions"
>
<div
class=
"item disabled row-controls-previous"
><i
class=
"mdi mdi-chevron-left"
></i></div>
<div
class=
"item disabled row-controls-previous"
><i
class=
"mdi mdi-chevron-left"
></i></div>
<div
class=
"item row-controls-next"
><i
class=
"mdi mdi-chevron-right"
></i></div>
<div
class=
"item row-controls-next"
><i
class=
"mdi mdi-chevron-right"
></i></div>
</div>
</div>
...
@@ -20,73 +20,74 @@
...
@@ -20,73 +20,74 @@
export
default
{
export
default
{
name
:
'
SongRow
'
,
name
:
'
SongRow
'
,
props
:
[
props
:
[
'
title
'
'
title
'
,
'
noactions
'
]
]
}
}
</
script
>
</
script
>
<
style
scoped
lang=
"less"
>
<
style
scoped
lang=
"less"
>
.song-row {
.song-row {
display: grid;
grid-template-rows: auto 1fr;
grid-gap: 5px;
& .song-header {
display: grid;
display: grid;
grid-template-columns: 1fr auto;
grid-template-rows: auto 1fr;
grid-gap: 5px;
& .row-title {
letter-spacing: 0.25em;
font-size: 14px;
font-weight: bold;
text-transform: uppercase;
&.row-title-noactions {
& .song-header {
margin: 10px 0px;
}
}
& .row-controls {
display: grid;
display: grid;
grid-template-columns: 1fr 1fr;
grid-template-columns: 1fr auto;
grid-gap: 15px;
& .item {
& .row-title {
width: 28px;
letter-spacing: 0.25em;
height: 28px;
font-size: 14px;
font-size: 22px;
font-weight: bold;
border-radius: 50%;
text-transform: uppercase;
display: flex;
justify-content: center;
align-items: center;
&.
disabled
{
&.
row-title-noactions
{
opacity: 0.4
;
margin: 10px 0px
;
}
}
}
& .row-controls {
display: grid;
grid-template-columns: 1fr 1fr;
grid-gap: 15px;
& .item {
width: 28px;
height: 28px;
font-size: 22px;
border-radius: 50%;
display: flex;
justify-content: center;
align-items: center;
&:not(.disabled):hover {
&.disabled {
background: rgba(255,255,255,0.2);
opacity: 0.4;
cursor: pointer;
}
&:not(.disabled):hover {
background: rgba(255,255,255,0.2);
cursor: pointer;
}
}
}
}
}
}
}
}
& .song-list {
& .song-list {
display: grid;
display: grid;
grid-template-columns: 1fr 1fr 1fr 1fr 1fr 1fr;
grid-template-columns: 1fr 1fr 1fr 1fr 1fr 1fr;
grid-gap: 15px;
grid-gap: 15px;
}
}
& .song-list-noresults {
& .song-list-noresults {
display: none;
display: none;
background: rgba(255,255,255,0.1);
background: rgba(255,255,255,0.1);
border-radius: 6px;
border-radius: 6px;
padding: 25px;
padding: 25px;
opacity: 0.6;
opacity: 0.6;
text-align: center;
text-align: center;
&.active {
&.active {
display: block;
display: block;
}
}
}
}
}
}
</
style
>
</
style
>
src/components/User/UserItem.vue
0 → 100644
View file @
1bca3c81
<
template
>
<!--
<div
class=
"song-item"
v-on:contextmenu=
"showContextMenu($event)"
>
<div
class=
"song-cover"
:style=
"'background-image: url(' + cover + '), url(' + require('@/assets/img/defaultAlbumArt.jpg') + ');'"
>
<div
class=
"song-charter-info"
>
<div
class=
"song-charter"
><i
class=
"mdi mdi-account-circle"
></i><span>
{{
charter
}}
</span></div>
</div>
</div>
<div
class=
"song-metadata"
>
<div
class=
"song-title"
>
{{
title
}}
</div>
<div
class=
"song-artist"
>
{{
artist
}}
</div>
<div
class=
"song-difficulties"
>
<img
src=
"@/assets/img/difficultyEasy.svg"
:class=
"hasEasyDifficulty ? 'active' : ''"
/>
<img
src=
"@/assets/img/difficultyNormal.svg"
:class=
"hasNormalDifficulty ? 'active' : ''"
/>
<img
src=
"@/assets/img/difficultyHard.svg"
:class=
"hasHardDifficulty ? 'active' : ''"
/>
<img
src=
"@/assets/img/difficultyExtreme.svg"
:class=
"hasExtremeDifficulty ? 'active' : ''"
/>
<img
src=
"@/assets/img/difficultyXD.svg"
:class=
"hasXDDifficulty ? 'active' : ''"
/>
</div>
</div>
</div>
-->
<div
class=
"user-item"
>
<div
class=
"user-avatar"
:style=
"'background-image: url(' + avatar + '), url(' + require('@/assets/img/defaultAvatar.jpg') + ');'"
></div>
<div
class=
"user-metadata"
>
<div
class=
"user-username"
>
{{
username
}}
</div>
</div>
</div>
</
template
>
<
script
>
import
{
remote
}
from
'
electron
'
;
const
{
clipboard
}
=
remote
;
export
default
{
name
:
'
Useritem
'
,
props
:
[
'
id
'
,
'
avatar
'
,
'
username
'
,
'
isPatreon
'
,
'
isVerified
'
],
data
:
function
()
{
return
{
isContextMenuActive
:
false
}
},
mounted
:
function
()
{
},
methods
:
{
showContextMenu
:
function
(
e
)
{
this
.
$root
.
$emit
(
'
showContextMenu
'
,
{
x
:
e
.
pageX
,
y
:
e
.
pageY
,
items
:
[
{
icon
:
"
eye
"
,
title
:
"
Open
"
,
method
:
function
()
{
alert
(
'
TODO
'
)
}.
bind
(
this
)
},
{
icon
:
"
link
"
,
title
:
"
Copy Link
"
,
method
:
function
()
{
clipboard
.
writeText
(
'
https://spinsha.re/song/
'
+
this
.
$props
.
id
)
}.
bind
(
this
)
},
{
icon
:
"
download
"
,
title
:
"
Download
"
,
method
:
function
()
{
this
.
$root
.
$emit
(
'
download
'
,
this
.
$props
.
isVerified
);
}.
bind
(
this
)
}
]});
}
}
}
</
script
>
<
style
scoped
lang=
"less"
>
.user-item {
background: #383c3f;
transition: 0.2s ease-in-out transform, 0.2s ease-in-out box-shadow;
overflow: hidden;
border-radius: 6px;
display: grid;
padding: 10px;
grid-gap: 15px;
grid-template-columns: 32px 1fr;
& .user-avatar {
background: rgba(255,255,255,0.1);
background-size: cover;
background-position: center;
width: 32px;
height: 32px;
border-radius: 32px;
}
& .user-metadata {
display: flex;
align-items: center;
& .user-name {
font-weight: bold;
overflow: hidden;
white-space: nowrap;
}
& .user-badge {
font-size: 18px;
margin-left: 10px;
}
}
&:hover {
transform: scale(1.1);
cursor: pointer;
box-shadow: 0px 4px 20px 5px rgba(0, 0, 0, 0.4);
}
}
</
style
>
src/components/User/UserRow.vue
0 → 100644
View file @
1bca3c81
<
template
>
<div
class=
"user-row"
>
<div
class=
"user-header"
>
<div
:class=
"'row-title ' + (noactions ? 'row-title-noactions' : '')"
>
{{
title
}}
</div>
<div
class=
"row-controls"
v-if=
"!noactions"
>
<div
class=
"item disabled row-controls-previous"
><i
class=
"mdi mdi-chevron-left"
></i></div>
<div
class=
"item row-controls-next"
><i
class=
"mdi mdi-chevron-right"
></i></div>
</div>
</div>
<div
class=
"user-list"
>
<slot></slot>
</div>
</div>
</
template
>
<
script
>
import
{
remote
}
from
'
electron
'
;
const
{
shell
}
=
remote
;
export
default
{
name
:
'
UserRow
'
,
props
:
[
'
title
'
,
'
noactions
'
]
}
</
script
>
<
style
scoped
lang=
"less"
>
.user-row {
display: grid;
grid-template-rows: auto 1fr;
grid-gap: 5px;
& .user-header {
display: grid;
grid-template-columns: 1fr auto;
& .row-title {
letter-spacing: 0.25em;
font-size: 14px;
font-weight: bold;
text-transform: uppercase;
&.row-title-noactions {
margin: 10px 0px;
}
}
}
& .user-list {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
grid-gap: 15px;
}
}
</
style
>
src/modules/module.api.js
View file @
1bca3c81
...
@@ -122,6 +122,22 @@ class SSAPI {
...
@@ -122,6 +122,22 @@ class SSAPI {
});
});
}
}
async
searchAll
()
{
let
apiPath
=
this
.
apiBase
+
"
searchAll
"
;
let
supportedVersion
=
this
.
supportedVersion
;
return
axios
.
get
(
apiPath
)
.
then
(
function
(
response
)
{
if
(
response
.
data
.
version
!==
supportedVersion
)
{
throw
new
Error
(
"
Client is outdated!
"
);
}
return
response
.
data
;
}).
catch
(
function
(
error
)
{
throw
new
Error
(
error
);
});
}
async
search
(
_searchQuery
)
{
async
search
(
_searchQuery
)
{
let
apiPath
=
this
.
apiBase
+
"
search/
"
+
_searchQuery
;
let
apiPath
=
this
.
apiBase
+
"
search/
"
+
_searchQuery
;
let
supportedVersion
=
this
.
supportedVersion
;
let
supportedVersion
=
this
.
supportedVersion
;
...
...
src/modules/module.srxd.js
View file @
1bca3c81
const
glob
=
require
(
'
glob
'
);
const
fs
=
require
(
'
fs
'
);
const
fs
=
require
(
'
fs
'
);
const
path
=
require
(
'
path
'
);
const
rimraf
=
require
(
'
rimraf
'
);
const
rimraf
=
require
(
'
rimraf
'
);
const
unzipper
=
require
(
'
unzipper
'
);
const
unzipper
=
require
(
'
unzipper
'
);
const
uniqid
=
require
(
'
uniqid
'
);
const
uniqid
=
require
(
'
uniqid
'
);
const
UserSettings
=
require
(
'
./module.usersettings
'
);
class
SRXD
{
class
SRXD
{
constructor
()
{
constructor
()
{
...
@@ -9,6 +12,8 @@ class SRXD {
...
@@ -9,6 +12,8 @@ class SRXD {
this
.
srtbLocation
=
""
;
this
.
srtbLocation
=
""
;
this
.
songTrackInfo
=
{};
this
.
songTrackInfo
=
{};
this
.
songLocation
=
""
;
this
.
songLocation
=
""
;
this
.
userSettings
=
new
UserSettings
();
}
}
// Extract a local backup folder
// Extract a local backup folder
...
@@ -79,11 +84,11 @@ class SRXD {
...
@@ -79,11 +84,11 @@ class SRXD {
this
.
songLocation
=
""
;
this
.
songLocation
=
""
;
}
}
getSongDetail
(
srtbPath
)
{
getSongDetail
(
rootPath
,
songPath
)
{
let
srtbPath
=
path
.
join
(
rootPath
,
songPath
);
let
srtbFile
=
JSON
.
parse
(
fs
.
readFileSync
(
srtbPath
)
);
let
srtbFile
=
JSON
.
parse
(
fs
.
readFileSync
(
srtbPath
)
);
let
songTrackInfo
=
""
;
let
songTrackInfo
=
""
;
let
songOggInfo
=
""
;
let
songOggInfo
=
""
;
srtbFile
.
largeStringValuesContainer
.
values
.
forEach
(
function
(
value
)
{
srtbFile
.
largeStringValuesContainer
.
values
.
forEach
(
function
(
value
)
{
if
(
value
.
key
==
"
SO_TrackInfo_TrackInfo
"
)
{
if
(
value
.
key
==
"
SO_TrackInfo_TrackInfo
"
)
{
...
@@ -114,10 +119,10 @@ class SRXD {
...
@@ -114,10 +119,10 @@ class SRXD {
else
{
return
[];}
else
{
return
[];}
}
}
getSongCover
(
fileName
)
{
getSongCover
(
fileName
)
{
let
fileExtension
=
this
.
getFileExtension
(
fileName
,
path
.
join
(
userSettings
.
get
(
'
gameDirectory
'
),
"
AlbumArt
"
)
);
let
fileExtension
=
this
.
getFileExtension
(
fileName
,
path
.
join
(
this
.
userSettings
.
get
(
'
gameDirectory
'
),
"
AlbumArt
"
)
);
if
(
fileExtension
.
length
>
0
)
{
if
(
fileExtension
.
length
>
0
)
{
let
finalPath
=
path
.
join
(
userSettings
.
get
(
'
gameDirectory
'
),
"
AlbumArt
"
,
fileExtension
[
0
]);
let
finalPath
=
path
.
join
(
this
.
userSettings
.
get
(
'
gameDirectory
'
),
"
AlbumArt
"
,
fileExtension
[
0
]);
let
base64Data
=
"
data:image/jpg;base64,
"
+
fs
.
readFileSync
(
finalPath
,
{
encoding
:
'
base64
'
});
let
base64Data
=
"
data:image/jpg;base64,
"
+
fs
.
readFileSync
(
finalPath
,
{
encoding
:
'
base64
'
});
...
@@ -133,9 +138,9 @@ class SRXD {
...
@@ -133,9 +138,9 @@ class SRXD {
//Gets directory of files to delete
//Gets directory of files to delete
getSongAssetDirectory
(
fileName
,
fileType
)
{
getSongAssetDirectory
(
fileName
,
fileType
)
{
let
fileExtension
=
this
.
getFileExtension
(
fileName
,
path
.
join
(
userSettings
.
get
(
'
gameDirectory
'
),
fileType
));
let
fileExtension
=
this
.
getFileExtension
(
fileName
,
path
.
join
(
this
.
userSettings
.
get
(
'
gameDirectory
'
),
fileType
));
if
(
fileExtension
.
join
()
!=
''
)
{
if
(
fileExtension
.
join
()
!=
''
)
{
let
finalPath
=
path
.
join
(
userSettings
.
get
(
'
gameDirectory
'
),
fileType
,
fileExtension
.
join
());
let
finalPath
=
path
.
join
(
this
.
userSettings
.
get
(
'
gameDirectory
'
),
fileType
,
fileExtension
.
join
());
return
finalPath
;
return
finalPath
;
}
}
else
{
return
fileName
;}
else
{
return
fileName
;}
...
...
src/modules/module.usersettings.js
0 → 100644
View file @
1bca3c81
const
{
electron
,
remote
}
=
require
(
'
electron
'
);
const
app
=
remote
.
app
;
const
path
=
require
(
'
path
'
);
const
fs
=
require
(
'
fs
'
);
class
UserSettings
{
constructor
()
{
let
defaults
=
{
showExplicit
:
false
,
gameDirectory
:
detectGameDirectory
(),
language
:
app
.
getLocale
()
};
const
userDataPath
=
app
.
getPath
(
'
userData
'
);
this
.
path
=
path
.
join
(
userDataPath
,
'
UserSettings.json
'
);
this
.
data
=
parseDataFile
(
this
.
path
,
defaults
);
}
get
(
key
)
{
return
this
.
data
[
key
];
}
set
(
key
,
val
)
{
this
.
data
[
key
]
=
val
;
this
.
write
();
}
clear
(
key
)
{
this
.
data
[
key
]
=
null
;
this
.
write
();
}
write
()
{
fs
.
writeFileSync
(
this
.
path
,
JSON
.
stringify
(
this
.
data
));
}
}
function
parseDataFile
(
filePath
,
defaults
)
{
try
{
return
JSON
.
parse
(
fs
.
readFileSync
(
filePath
));
}
catch
(
error
)
{
return
defaults
;
}
}
// TODO: Mac/Linux Support
function
detectGameDirectory
()
{
if
(
process
.
platform
==
"
win32
"
)
{
return
path
.
join
(
app
.
getPath
(
"
userData
"
),
"
../..
"
,
"
LocalLow
"
,
"
Super Spin Digital
"
,
"
Spin Rhythm XD
"
,
"
Custom
"
);
}
else
{
console
.
error
(
"
Unsupported system
"
);
}
}
module
.
exports
=
UserSettings
;
\ No newline at end of file
src/views/Library.vue
View file @
1bca3c81
<
template
>
<
template
>
<section
class=
"section-library"
>
<section
class=
"section-library"
>
LIBRARY
<SongRow
title=
"Installed Songs"
noactions=
"true"
>
<SongInstallItem
/>
<SongLocalItem
v-for=
"song in librarySongs"
v-bind:key=
"song.detail.id"
v-bind=
"song"
/>
</SongRow>
</section>
</section>
</
template
>
</
template
>
<
script
>
<
script
>
import
glob
from
'
glob
'
;
import
fs
from
'
fs
'
;
import
path
from
'
path
'
;
import
UserSettings
from
'
@/modules/module.usersettings.js
'
;
import
SRXD
from
'
@/modules/module.srxd.js
'
;
import
SongRow
from
'
@/components/Song/SongRow.vue
'
;
import
SongLocalItem
from
'
@/components/Song/SongLocalItem.vue
'
;
import
SongInstallItem
from
'
@/components/Song/SongInstallItem.vue
'
;
export
default
{
export
default
{
name
:
'
Library
'
,
name
:
'
Library
'
,
data
:
function
()
{
return
{
librarySongs
:
[]
}
},
components
:
{
components
:
{
SongRow
,
SongLocalItem
,
SongInstallItem
},
mounted
:
function
()
{
this
.
refreshLibrary
();
},
methods
:
{
refreshLibrary
:
function
()
{
let
userSettings
=
new
UserSettings
();
this
.
$data
.
librarySongs
=
[];
// Load local .srtb
glob
(
path
.
join
(
userSettings
.
get
(
'
gameDirectory
'
),
"
*.srtb
"
),
(
error
,
files
)
=>
{
files
.
forEach
((
file
)
=>
{
// Get Detail Data
let
songDetail
=
this
.
getSongDetail
(
file
);
let
songCover
=
glob
.
sync
(
path
.
join
(
userSettings
.
get
(
'
gameDirectory
'
),
"
AlbumArt
"
,
songDetail
.
coverReference
+
"
.*
"
))[
0
];
let
songSpinShareReference
=
false
;
if
(
file
.
split
(
"
/
"
)[
file
.
split
(
"
/
"
).
length
-
1
].
replace
(
"
.srtb
"
,
""
).
includes
(
"
spinshare_
"
))
{
songSpinShareReference
=
file
.
split
(
"
/
"
)[
file
.
split
(
"
/
"
).
length
-
1
].
replace
(
"
.srtb
"
,
""
);
}
if
(
songCover
)
{
songCover
=
"
data:image/jpg;base64,
"
+
fs
.
readFileSync
(
songCover
,
{
encoding
:
'
base64
'
});
}
let
librarySong
=
{
detail
:
songDetail
,
cover
:
songCover
,
isSpinShare
:
songSpinShareReference
};
this
.
$data
.
librarySongs
.
push
(
librarySong
)
});
});
},
getSongDetail
:
function
(
filePath
)
{
let
srtbContent
=
JSON
.
parse
(
fs
.
readFileSync
(
filePath
)
);
let
trackInfo
=
{};
let
stringValueContainers
=
srtbContent
[
'
largeStringValuesContainer
'
].
values
;
stringValueContainers
.
forEach
((
stringValueContainer
)
=>
{
if
(
stringValueContainer
.
key
==
"
SO_TrackInfo_TrackInfo
"
)
{
let
rawTrackInfo
=
JSON
.
parse
(
stringValueContainer
.
val
);
trackInfo
=
{
title
:
rawTrackInfo
.
title
,
artist
:
rawTrackInfo
.
artistName
,
charter
:
rawTrackInfo
.
charter
,
coverReference
:
rawTrackInfo
.
albumArtReference
.
assetName
};
}
});
return
trackInfo
;
}
}
}
}
}
</
script
>
</
script
>
...
...
src/views/Search.vue
View file @
1bca3c81
...
@@ -4,22 +4,86 @@
...
@@ -4,22 +4,86 @@
<div
class=
"show-all"
>
<div
class=
"show-all"
>
<div
class=
"button button-label"
v-on:click=
"searchAll()"
locale=
""
>
Show all
</div>
<div
class=
"button button-label"
v-on:click=
"searchAll()"
locale=
""
>
Show all
</div>
</div>
</div>
<input
type=
"search"
placeholder=
"Search for songs, tags & profiles..."
localeplaceholder=
""
v-on:change=
"search(this.value)
"
>
<input
type=
"search"
placeholder=
"Search for songs, tags & profiles..."
v-on:input=
"search()"
v-model=
"searchQuery
"
>
</div>
</div>
<div
class=
"search-results"
>
<UserRow
title=
"Users"
noactions=
"true"
v-if=
"searchResultsUsers.length > 0"
>
<UserItem
v-for=
"user in searchResultsUsers"
v-bind:key=
"user.id"
v-bind=
"user"
/>
</UserRow>
<SongRow
title=
"Songs"
noactions=
"true"
v-if=
"searchResultsSongs.length > 0"
>
<SongItem
v-for=
"song in searchResultsSongs"
v-bind:key=
"song.id"
v-bind=
"song"
/>
</SongRow>
<div
class=
"search-results-noresults"
v-if=
"searchResultsUsers.length == 0 && searchResultsSongs.length == 0 && apiFinished"
>
<div
class=
"noresults-title"
>
{{
searchQuery
}}
</div>
<div
class=
"noresults-text"
>
Your search did not match any songs or users. Make sure, that all words are spelled correctly or try a different query.
</div>
</div>
</div>
</section>
</section>
</
template
>
</
template
>
<
script
>
<
script
>
import
SSAPI
from
'
@/modules/module.api.js
'
;
import
UserRow
from
'
@/components/User/UserRow.vue
'
;
import
UserItem
from
'
@/components/User/UserItem.vue
'
;
import
SongRow
from
'
@/components/Song/SongRow.vue
'
;
import
SongItem
from
'
@/components/Song/SongItem.vue
'
;
export
default
{
export
default
{
name
:
'
Search
'
,
name
:
'
Search
'
,
components
:
{
components
:
{
UserRow
,
UserItem
,
SongRow
,
SongItem
},
data
:
function
()
{
return
{
searchQuery
:
""
,
searchResultsUsers
:
[],
searchResultsSongs
:
[],
apiFinished
:
false
}
},
},
methods
:
{
methods
:
{
searchAll
:
function
()
{
searchAll
:
function
()
{
let
ssapi
=
new
SSAPI
(
process
.
env
.
NODE_ENV
===
'
development
'
);
this
.
$data
.
searchQuery
==
""
;
this
.
$data
.
apiFinished
=
false
;
ssapi
.
searchAll
().
then
((
data
)
=>
{
if
(
data
.
status
==
200
)
{
this
.
$data
.
searchResultsUsers
=
data
.
data
.
users
;
this
.
$data
.
searchResultsSongs
=
data
.
data
.
songs
;
this
.
$data
.
apiFinished
=
true
;
}
});
},
},
search
:
function
(
searchQuery
)
{
search
:
function
()
{
let
ssapi
=
new
SSAPI
(
process
.
env
.
NODE_ENV
===
'
development
'
);
this
.
$data
.
apiFinished
=
false
;
if
(
this
.
$data
.
searchQuery
!=
""
)
{
ssapi
.
search
(
this
.
$data
.
searchQuery
).
then
((
data
)
=>
{
if
(
data
.
status
==
200
)
{
this
.
$data
.
searchResultsUsers
=
data
.
data
.
users
;
this
.
$data
.
searchResultsSongs
=
data
.
data
.
songs
;
this
.
$data
.
apiFinished
=
true
;
}
});
}
else
{
this
.
$data
.
searchResultsUsers
=
[];
this
.
$data
.
searchResultsSongs
=
[];
this
.
$data
.
apiFinished
=
false
;
}
}
}
}
}
}
}
...
@@ -37,20 +101,28 @@
...
@@ -37,20 +101,28 @@
border-radius: 4px;
border-radius: 4px;
display: grid;
display: grid;
grid-template-columns: auto 1fr;
grid-template-columns: auto 1fr;
& .show-all {
height: 100%;
display: flex;
justify-content: center;
align-items: center;
padding: 0px 10px;
}
input {
input {
width: 100%;
width: 100%;
font-family: 'Open Sans', sans-serif;
font-family: 'Open Sans', sans-serif;
font-size: 14px;
font-size: 14px;
background: transparent;
color: #fff;
color: #fff;
background: rgba(255,255,255,0.2);
border-radius: 4px;
border-radius: 4px;
padding: 14px 28px;
padding: 14px 28px;
border: 0px;
border: 0px;
transition: 0.2s ease-in-out all;
transition: 0.2s ease-in-out all;
&:hover {
&:hover {
background: rgba(255,255,255,0.
4
);
background: rgba(255,255,255,0.
2
);
color: #fff;
color: #fff;
}
}
&:focus {
&:focus {
...
@@ -67,26 +139,18 @@
...
@@ -67,26 +139,18 @@
grid-gap: 25px;
grid-gap: 25px;
& .search-results-users {
& .search-results-users {
display: none;
display: grid;
&.active {
display: grid;
}
}
}
& .search-results-songs {
& .search-results-songs {
display: none;
display: grid;
&.active {
display: grid;
}
}
}
& .search-results-noresults {
& .search-results-noresults {
background: rgba(255,255,255,0.1);
background: rgba(255,255,255,0.1);
border-radius: 6px;
border-radius: 6px;
padding: 25px;
padding: 25px;
display:
none
;
display:
block
;
& .noresults-title {
& .noresults-title {
font-size: 24px;
font-size: 24px;
...
@@ -96,10 +160,6 @@
...
@@ -96,10 +160,6 @@
& .noresults-text {
& .noresults-text {
opacity: 0.6;
opacity: 0.6;
}
}
&.active {
display: block;
}
}
}
}
}
}
}
...
...
src/views/Startup.vue
View file @
1bca3c81
...
@@ -3,7 +3,8 @@
...
@@ -3,7 +3,8 @@
<div
class=
"staff-promos"
>
<div
class=
"staff-promos"
>
<StaffPromoPlaceholder
<StaffPromoPlaceholder
v-if=
"isPromoLoading"
v-if=
"isPromoLoading"
v-for=
"n in 2"
/>
v-for=
"n in 2"
v-bind:key=
"n"
/>
<StaffPromo
<StaffPromo
v-if=
"!isPromoLoading"
v-if=
"!isPromoLoading"
v-for=
"staffPromo in staffPromos"
v-for=
"staffPromo in staffPromos"
...
@@ -15,7 +16,8 @@
...
@@ -15,7 +16,8 @@
title=
"New Songs"
>
title=
"New Songs"
>
<SongItemPlaceholder
<SongItemPlaceholder
v-if=
"isNewSongsLoading"
v-if=
"isNewSongsLoading"
v-for=
"n in 6"
/>
v-for=
"n in 6"
v-bind:key=
"n"
/>
<SongItem
<SongItem
v-if=
"!isNewSongsLoading"
v-if=
"!isNewSongsLoading"
v-for=
"song in newSongs"
v-for=
"song in newSongs"
...
@@ -27,7 +29,8 @@
...
@@ -27,7 +29,8 @@
title=
"Popular Songs"
>
title=
"Popular Songs"
>
<SongItemPlaceholder
<SongItemPlaceholder
v-if=
"isPopularSongsLoading"
v-if=
"isPopularSongsLoading"
v-for=
"n in 6"
/>
v-for=
"n in 6"
v-bind:key=
"n"
/>
<SongItem
<SongItem
v-if=
"!isPopularSongsLoading"
v-if=
"!isPopularSongsLoading"
v-for=
"song in popularSongs"
v-for=
"song in popularSongs"
...
@@ -60,7 +63,7 @@
...
@@ -60,7 +63,7 @@
}
}
},
},
mounted
:
function
()
{
mounted
:
function
()
{
let
ssapi
=
new
SSAPI
(
true
);
let
ssapi
=
new
SSAPI
(
process
.
env
.
NODE_ENV
===
'
development
'
);
ssapi
.
getPromos
().
then
((
data
)
=>
{
ssapi
.
getPromos
().
then
((
data
)
=>
{
this
.
$data
.
isPromoLoading
=
false
;
this
.
$data
.
isPromoLoading
=
false
;
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment