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
SpinShare
Desktop Client
Commits
0fbd4383
Commit
0fbd4383
authored
Apr 23, 2020
by
SpinShare
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
library removal, frontpage pagination
parent
1bca3c81
Changes
10
Hide whitespace changes
Inline
Side-by-side
Showing
10 changed files
with
271 additions
and
45 deletions
+271
-45
src/App.vue
src/App.vue
+60
-2
src/components/Overlays/DeleteOverlay.vue
src/components/Overlays/DeleteOverlay.vue
+94
-0
src/components/Song/SongItem.vue
src/components/Song/SongItem.vue
+3
-3
src/components/Song/SongItemPlaceholder.vue
src/components/Song/SongItemPlaceholder.vue
+19
-0
src/components/Song/SongLocalItem.vue
src/components/Song/SongLocalItem.vue
+2
-1
src/components/Song/SongRow.vue
src/components/Song/SongRow.vue
+2
-3
src/components/User/UserRow.vue
src/components/User/UserRow.vue
+3
-11
src/views/Library.vue
src/views/Library.vue
+13
-6
src/views/Search.vue
src/views/Search.vue
+1
-1
src/views/Startup.vue
src/views/Startup.vue
+74
-18
No files found.
src/App.vue
View file @
0fbd4383
...
@@ -7,33 +7,91 @@
...
@@ -7,33 +7,91 @@
<ContextMenu>
<ContextMenu>
</ContextMenu>
</ContextMenu>
<DeleteOverlay
v-if=
"showDeleteOverlay"
v-bind:deleteFiles=
"deleteFiles"
/>
</div>
</div>
</
template
>
</
template
>
<
script
>
<
script
>
import
fs
from
'
fs
'
;
import
glob
from
'
glob
'
;
import
path
from
'
path
'
;
import
UserSettings
from
'
@/modules/module.usersettings.js
'
;
import
Navigation
from
'
@/components/Navigation/Navigation.vue
'
;
import
Navigation
from
'
@/components/Navigation/Navigation.vue
'
;
import
ContextMenu
from
'
@/components/ContextMenu/ContextMenu.vue
'
;
import
ContextMenu
from
'
@/components/ContextMenu/ContextMenu.vue
'
;
import
DeleteOverlay
from
'
@/components/Overlays/DeleteOverlay.vue
'
;
export
default
{
export
default
{
name
:
'
App
'
,
name
:
'
App
'
,
components
:
{
components
:
{
Navigation
,
Navigation
,
ContextMenu
ContextMenu
,
DeleteOverlay
},
},
data
:
function
()
{
data
:
function
()
{
return
{
return
{
downloadQueue
:
[]
downloadQueue
:
[],
showDeleteOverlay
:
false
,
deleteFiles
:
[]
}
}
},
},
mounted
:
function
()
{
mounted
:
function
()
{
this
.
$root
.
$on
(
'
download
'
,
(
url
)
=>
{
this
.
$root
.
$on
(
'
download
'
,
(
url
)
=>
{
this
.
addToQueue
(
url
);
this
.
addToQueue
(
url
);
});
});
this
.
$root
.
$on
(
'
delete
'
,
(
file
)
=>
{
this
.
$data
.
showDeleteOverlay
=
true
;
this
.
$data
.
deleteFiles
=
this
.
getConnectedFiles
(
file
);
console
.
log
(
this
.
$data
.
deleteFiles
);
});
this
.
$root
.
$on
(
'
deleteDeny
'
,
()
=>
{
this
.
$data
.
showDeleteOverlay
=
false
;
this
.
$data
.
deleteFiles
=
""
;
});
this
.
$root
.
$on
(
'
deleteConfirmed
'
,
()
=>
{
this
.
$data
.
deleteFiles
.
forEach
((
file
)
=>
{
fs
.
unlinkSync
(
file
);
});
this
.
$data
.
showDeleteOverlay
=
false
;
this
.
$data
.
deleteFiles
=
""
;
});
},
},
methods
:
{
methods
:
{
addToQueue
:
function
(
url
)
{
addToQueue
:
function
(
url
)
{
this
.
$data
.
downloadQueue
.
push
(
url
);
this
.
$data
.
downloadQueue
.
push
(
url
);
console
.
log
(
"
Added
"
+
url
+
"
to queue
"
);
console
.
log
(
"
Added
"
+
url
+
"
to queue
"
);
},
getConnectedFiles
:
function
(
srtbFilePath
)
{
let
userSettings
=
new
UserSettings
();
let
srtbContent
=
JSON
.
parse
(
fs
.
readFileSync
(
srtbFilePath
)
);
let
connectedFiles
=
[];
connectedFiles
.
push
(
srtbFilePath
);
let
stringValueContainers
=
srtbContent
[
'
largeStringValuesContainer
'
].
values
;
stringValueContainers
.
forEach
((
stringValueContainer
)
=>
{
if
(
stringValueContainer
.
key
==
"
SO_TrackInfo_TrackInfo
"
)
{
let
rawTrackInfo
=
JSON
.
parse
(
stringValueContainer
.
val
);
let
coverPath
=
glob
.
sync
(
path
.
join
(
userSettings
.
get
(
'
gameDirectory
'
),
"
AlbumArt
"
,
rawTrackInfo
.
albumArtReference
.
assetName
+
"
.*
"
))[
0
];
if
(
coverPath
)
{
connectedFiles
.
push
(
coverPath
);
}
}
if
(
stringValueContainer
.
key
.
includes
(
"
SO_ClipInfo
"
))
{
let
rawClipInfo
=
JSON
.
parse
(
stringValueContainer
.
val
);
let
clipPath
=
glob
.
sync
(
path
.
join
(
userSettings
.
get
(
'
gameDirectory
'
),
"
AudioClips
"
,
rawClipInfo
.
clipAssetReference
.
assetName
+
"
.*
"
))[
0
];
if
(
clipPath
)
{
connectedFiles
.
push
(
clipPath
);
}
}
});
return
connectedFiles
;
}
}
},
},
watch
:
{
watch
:
{
...
...
src/components/Overlays/DeleteOverlay.vue
0 → 100644
View file @
0fbd4383
<
template
>
<div
class=
"delete-overlay"
>
<div
class=
"delete-content"
>
<div
class=
"delete-main"
>
<div
class=
"delete-title"
>
library.deletemodal.title
</div>
<div
class=
"delete-text"
>
library.deletemodal.text
</div>
<div
class=
"delete-files"
>
<span
v-for=
"deleteFile in deleteFiles"
>
{{
deleteFile
}}
</span>
</div>
</div>
<div
class=
"delete-actions"
>
<button
class=
"button"
>
Delete
</button>
<button
class=
"button"
v-on:click=
"close()"
>
Close
</button>
</div>
</div>
</div>
</
template
>
<
script
>
import
{
remote
}
from
'
electron
'
;
const
{
shell
}
=
remote
;
export
default
{
name
:
'
DeleteOverlay
'
,
props
:
[
'
deleteFiles
'
],
methods
:
{
confirm
()
{
this
.
$root
.
$emit
(
'
deleteConfirm
'
);
},
close
()
{
this
.
$root
.
$emit
(
'
deleteDeny
'
);
}
}
}
</
script
>
<
style
scoped
lang=
"less"
>
.delete-overlay {
position: fixed;
z-index: 100;
top: 0px;
left: 0px;
right: 0px;
bottom: 0px;
background: rgba(0,0,0,0.75);
display: flex;
justify-content: center;
align-items: center;
& .delete-content {
width: 500px;
background: #212629;
border-radius: 6px;
position: relative;
overflow: hidden;
& .delete-main {
padding: 25px;
& .delete-title {
letter-spacing: 0.25em;
font-size: 14px;
font-weight: bold;
text-transform: uppercase;
}
& .delete-text {
padding: 15px 0px;
opacity: 0.6;
}
& .delete-files {
font-size: 12px;
& span {
display: block;
padding: 5px 0px;
}
}
}
& .delete-actions {
display: flex;
justify-content: flex-end;
padding: 25px;
background: rgba(0,0,0,0.4);
& button {
margin-left: 10px;
}
}
}
}
</
style
>
src/components/Song/SongItem.vue
View file @
0fbd4383
...
@@ -52,9 +52,9 @@
...
@@ -52,9 +52,9 @@
x
:
e
.
pageX
,
x
:
e
.
pageX
,
y
:
e
.
pageY
,
y
:
e
.
pageY
,
items
:
[
items
:
[
{
icon
:
"
eye
"
,
title
:
"
Open
"
,
method
:
function
()
{
alert
(
'
TODO
'
)
}.
bind
(
this
)
},
{
icon
:
"
eye
"
,
title
:
"
Open
"
,
method
:
()
=>
{
alert
(
'
TODO
'
)
}
},
{
icon
:
"
link
"
,
title
:
"
Copy Link
"
,
method
:
function
()
{
clipboard
.
writeText
(
'
https://spinsha.re/song/
'
+
this
.
$props
.
id
)
}.
bind
(
this
)
},
{
icon
:
"
link
"
,
title
:
"
Copy Link
"
,
method
:
()
=>
{
clipboard
.
writeText
(
'
https://spinsha.re/song/
'
+
this
.
$props
.
id
)
}
},
{
icon
:
"
download
"
,
title
:
"
Download
"
,
method
:
function
()
{
this
.
$root
.
$emit
(
'
download
'
,
this
.
$props
.
zip
);
}.
bind
(
this
)
}
{
icon
:
"
download
"
,
title
:
"
Download
"
,
method
:
()
=>
{
this
.
$root
.
$emit
(
'
download
'
,
this
.
$props
.
zip
);
}
}
]});
]});
}
}
}
}
...
...
src/components/Song/SongItemPlaceholder.vue
View file @
0fbd4383
...
@@ -5,6 +5,7 @@
...
@@ -5,6 +5,7 @@
<div
class=
"song-title"
>
</div>
<div
class=
"song-title"
>
</div>
<div
class=
"song-artist"
>
</div>
<div
class=
"song-artist"
>
</div>
<div
class=
"song-difficulties"
>
<div
class=
"song-difficulties"
>
<img
src=
"@/assets/img/difficultyEasy.svg"
/>
</div>
</div>
</div>
</div>
</div>
</div>
...
@@ -41,14 +42,32 @@
...
@@ -41,14 +42,32 @@
& .song-metadata {
& .song-metadata {
padding: 15px;
padding: 15px;
& .song-title {
height: 19px;
}
& .song-artist {
& .song-artist {
margin-top: 5px;
margin-top: 5px;
height: 19px;
}
}
& .song-difficulties {
& .song-difficulties {
margin-top: 10px;
margin-top: 10px;
height: 20px;
height: 20px;
display: flex;
display: flex;
& img {
height: 18px;
margin-right: 10px;
opacity: 0;
}
}
}
}
}
}
}
@keyframes songLoadingShimmer {
from {
background-position: 0px 0px;
}
to {
background-position: 173px 0px;
}
}
</
style
>
</
style
>
src/components/Song/SongLocalItem.vue
View file @
0fbd4383
...
@@ -20,6 +20,7 @@
...
@@ -20,6 +20,7 @@
name
:
'
SongLocalItem
'
,
name
:
'
SongLocalItem
'
,
props
:
[
props
:
[
'
id
'
,
'
id
'
,
'
file
'
,
'
detail
'
,
'
detail
'
,
'
cover
'
,
'
cover
'
,
'
isSpinShare
'
'
isSpinShare
'
...
@@ -37,7 +38,7 @@
...
@@ -37,7 +38,7 @@
x
:
e
.
pageX
,
x
:
e
.
pageX
,
y
:
e
.
pageY
,
y
:
e
.
pageY
,
items
:
[
items
:
[
{
icon
:
"
delete
"
,
title
:
"
Delete
"
,
method
:
function
()
{
alert
(
'
TODO
'
)
}.
bind
(
this
)
}
{
icon
:
"
delete
"
,
title
:
"
Delete
"
,
method
:
()
=>
{
this
.
$root
.
$emit
(
'
delete
'
,
this
.
$props
.
file
);
}
}
]});
]});
}
}
}
}
...
...
src/components/Song/SongRow.vue
View file @
0fbd4383
...
@@ -3,12 +3,11 @@
...
@@ -3,12 +3,11 @@
<div
class=
"song-header"
>
<div
class=
"song-header"
>
<div
:class=
"'row-title ' + (noactions ? 'row-title-noactions' : '')"
>
{{
title
}}
</div>
<div
:class=
"'row-title ' + (noactions ? 'row-title-noactions' : '')"
>
{{
title
}}
</div>
<div
class=
"row-controls"
v-if=
"!noactions"
>
<div
class=
"row-controls"
v-if=
"!noactions"
>
<div
class=
"item disabled row-controls-previous"
><i
class=
"mdi mdi-chevron-left"
></i></div>
<slot
name=
"controls"
></slot>
<div
class=
"item row-controls-next"
><i
class=
"mdi mdi-chevron-right"
></i></div>
</div>
</div>
</div>
</div>
<div
class=
"song-list"
>
<div
class=
"song-list"
>
<slot></slot>
<slot
name=
"song-list"
></slot>
</div>
</div>
</div>
</div>
</
template
>
</
template
>
...
...
src/components/User/UserRow.vue
View file @
0fbd4383
<
template
>
<
template
>
<div
class=
"user-row"
>
<div
class=
"user-row"
>
<div
class=
"user-header"
>
<div
class=
"user-header"
>
<div
:class=
"'row-title ' + (noactions ? 'row-title-noactions' : '')"
>
{{
title
}}
</div>
<div
class=
"row-title"
>
{{
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>
<div
class=
"user-list"
>
<div
class=
"user-list"
>
<slot></slot>
<slot></slot>
...
@@ -20,8 +16,7 @@
...
@@ -20,8 +16,7 @@
export
default
{
export
default
{
name
:
'
UserRow
'
,
name
:
'
UserRow
'
,
props
:
[
props
:
[
'
title
'
,
'
title
'
'
noactions
'
]
]
}
}
</
script
>
</
script
>
...
@@ -41,10 +36,7 @@
...
@@ -41,10 +36,7 @@
font-size: 14px;
font-size: 14px;
font-weight: bold;
font-weight: bold;
text-transform: uppercase;
text-transform: uppercase;
margin: 10px 0px;
&.row-title-noactions {
margin: 10px 0px;
}
}
}
}
}
& .user-list {
& .user-list {
...
...
src/views/Library.vue
View file @
0fbd4383
<
template
>
<
template
>
<section
class=
"section-library"
>
<section
class=
"section-library"
>
<SongRow
title=
"Installed Songs"
noactions=
"true"
>
<SongRow
title=
"Installed Songs"
>
<SongInstallItem
/>
<template
v-slot:controls
>
<div
class=
"item"
></div>
<div
class=
"item"
v-on:click=
"refreshLibrary()"
><i
class=
"mdi mdi-refresh"
></i></div>
</
template
>
<
template
v-slot:song-list
>
<SongInstallItem
/>
<SongLocalItem
<SongLocalItem
v-for=
"song in librarySongs"
v-for=
"song in librarySongs"
v-bind:key=
"song.detail.id"
v-bind:key=
"song.detail.id"
v-bind=
"song"
/>
v-bind=
"song"
/>
</
template
>
</SongRow>
</SongRow>
</section>
</section>
</template>
</template>
...
@@ -61,6 +67,7 @@
...
@@ -61,6 +67,7 @@
}
}
let
librarySong
=
{
let
librarySong
=
{
file
:
file
,
detail
:
songDetail
,
detail
:
songDetail
,
cover
:
songCover
,
cover
:
songCover
,
isSpinShare
:
songSpinShareReference
isSpinShare
:
songSpinShareReference
...
...
src/views/Search.vue
View file @
0fbd4383
...
@@ -7,7 +7,7 @@
...
@@ -7,7 +7,7 @@
<input
type=
"search"
placeholder=
"Search for songs, tags & profiles..."
v-on:input=
"search()"
v-model=
"searchQuery"
>
<input
type=
"search"
placeholder=
"Search for songs, tags & profiles..."
v-on:input=
"search()"
v-model=
"searchQuery"
>
</div>
</div>
<div
class=
"search-results"
>
<div
class=
"search-results"
>
<UserRow
title=
"Users"
noactions=
"true"
v-if=
"searchResultsUsers.length > 0"
>
<UserRow
title=
"Users"
v-if=
"searchResultsUsers.length > 0"
>
<UserItem
<UserItem
v-for=
"user in searchResultsUsers"
v-for=
"user in searchResultsUsers"
v-bind:key=
"user.id"
v-bind:key=
"user.id"
...
...
src/views/Startup.vue
View file @
0fbd4383
...
@@ -14,28 +14,40 @@
...
@@ -14,28 +14,40 @@
<SongRow
<SongRow
title=
"New Songs"
>
title=
"New Songs"
>
<SongItemPlaceholder
<template
v-slot:controls
>
v-if=
"isNewSongsLoading"
<div
:class=
"'item ' + (newSongsOffset == 0 ? 'disabled' : '')"
v-on:click=
"newPrevious()"
><i
class=
"mdi mdi-chevron-left"
></i></div>
v-for=
"n in 6"
<div
:class=
"'item ' + (newSongs.length
<
5
?
'
disabled
'
:
'')"
v-on:click=
"newNext()"
><i
class=
"mdi mdi-chevron-right"
></i></div>
v-bind:key=
"n"
/>
</
template
>
<SongItem
<
template
v-slot:song-list
>
v-if=
"!isNewSongsLoading"
<SongItemPlaceholder
v-for=
"song in newSongs"
v-if=
"isNewSongsLoading"
v-bind:key=
"song.id"
v-for=
"n in 6"
v-bind=
"song"
/>
v-bind:key=
"n"
/>
<SongItem
v-if=
"!isNewSongsLoading"
v-for=
"song in newSongs"
v-bind:key=
"song.id"
v-bind=
"song"
/>
</
template
>
</SongRow>
</SongRow>
<SongRow
<SongRow
title=
"Popular Songs"
>
title=
"Popular Songs"
>
<SongItemPlaceholder
<
template
v-slot:controls
>
v-if=
"isPopularSongsLoading"
<div
:class=
"'item ' + (popularSongsOffset == 0 ? 'disabled' : '')"
v-on:click=
"popularPrevious()"
><i
class=
"mdi mdi-chevron-left"
></i></div>
v-for=
"n in 6"
<div
:class=
"'item ' + (popularSongs.length
<
5
?
'
disabled
'
:
'')"
v-on:click=
"popularNext()"
><i
class=
"mdi mdi-chevron-right"
></i></div>
v-bind:key=
"n"
/>
</
template
>
<SongItem
<
template
v-slot:song-list
>
v-if=
"!isPopularSongsLoading"
<SongItemPlaceholder
v-for=
"song in popularSongs"
v-if=
"isPopularSongsLoading"
v-bind:key=
"song.id"
v-for=
"n in 6"
v-bind=
"song"
/>
v-bind:key=
"n"
/>
<SongItem
v-if=
"!isPopularSongsLoading"
v-for=
"song in popularSongs"
v-bind:key=
"song.id"
v-bind=
"song"
/>
</
template
>
</SongRow>
</SongRow>
</section>
</section>
</template>
</template>
...
@@ -86,6 +98,50 @@
...
@@ -86,6 +98,50 @@
SongRow
,
SongRow
,
SongItem
,
SongItem
,
SongItemPlaceholder
SongItemPlaceholder
},
methods
:
{
newNext
:
function
()
{
if
(
this
.
$data
.
newSongs
.
length
>
5
)
{
this
.
$data
.
newSongsOffset
++
;
this
.
updateNew
();
}
},
newPrevious
:
function
()
{
if
(
this
.
$data
.
newSongsOffset
>
0
)
{
this
.
$data
.
newSongsOffset
--
;
this
.
updateNew
();
}
},
popularNext
:
function
()
{
if
(
this
.
$data
.
popularSongs
.
length
>
5
)
{
this
.
$data
.
popularSongsOffset
++
;
this
.
updatePopular
();
}
},
popularPrevious
:
function
()
{
if
(
this
.
$data
.
popularSongsOffset
>
0
)
{
this
.
$data
.
popularSongsOffset
--
;
this
.
updatePopular
();
}
},
updateNew
:
function
()
{
let
ssapi
=
new
SSAPI
(
process
.
env
.
NODE_ENV
===
'
development
'
);
this
.
$data
.
isNewSongsLoading
=
true
;
ssapi
.
getNewSongs
(
this
.
$data
.
newSongsOffset
).
then
((
data
)
=>
{
this
.
$data
.
isNewSongsLoading
=
false
;
this
.
$data
.
newSongs
=
data
;
});
},
updatePopular
:
function
()
{
let
ssapi
=
new
SSAPI
(
process
.
env
.
NODE_ENV
===
'
development
'
);
this
.
$data
.
isPopularSongsLoading
=
true
;
ssapi
.
getPopularSongs
(
this
.
$data
.
popularSongsOffset
).
then
((
data
)
=>
{
this
.
$data
.
isPopularSongsLoading
=
false
;
this
.
$data
.
popularSongs
=
data
;
});
}
}
}
}
}
</
script
>
</
script
>
...
...
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