...
 
Commits (144)
......@@ -20,65 +20,6 @@ var mouseDown; // Boolean : capture if the mouse is pressed
document.documentElement.scrollTop = 0; // For IE and Firefox
});
$('#volume').on('mouseleave', () => {
$('#volume').click();
});
$('button[action="command"], a[action="command"]').click(function (e) {
var name = $(this).attr('name');
var dataAjax = { command: name };
if ($(this).val() != '') dataAjax['options'] = $(this).val();
if (e.target.name == 'setVolume') {
var btn = $(e.target);
var val = parseInt(btn.val()), base = 100, pow = .76;
val = Math.pow(val, pow) / Math.pow(base, pow);
val = val * base;
dataAjax = { command: btn.attr('name'), options: val };
}
// ask for the kara list from given playlist
if (ajaxSearch[name]) ajaxSearch[name].abort();
ajaxSearch[name] = $.ajax({
url: 'admin/player',
type: 'PUT',
data: dataAjax
});
});
$('.btn[action="account"]').click(function () {
showProfil();
});
$('.btn[action="poweroff"]').click(function () {
$.ajax({
url: 'admin/shutdown',
type: 'POST',
}).done(function () {
DEBUG && console.log('Shutdown');
stopUpdate = true;
});
});
$('#adminMessage').click(function () {
displayModal('custom', 'Message indispensable',
'<select class="form-control" name="destination"><option value="screen">' + i18n.__('CL_SCREEN') + '</option>'
+ '<option value="users">' + i18n.__('CL_USERS') + '</option><option value="all">' + i18n.__('CL_ALL') + '</option></select>'
+ '<input type="text"name="duration" placeholder="5000 (ms)"/>'
+ '<input type="text" placeholder="Message" class="form-control" id="message" name="message">', function (data)
{
var defaultDuration = 5000;
var msgData = {
message: data.message,
destination: data.destination,
duration: !data.duration || isNaN(data.duration) ? defaultDuration : data.duration
};
ajx('POST', 'admin/player/message', msgData);
}
);
});
$('[name="searchPlaylist"]').keypress(function (e) { // allow pressing enter to validate a setting
if (e.which == 13) {
......@@ -359,10 +300,6 @@ var mouseDown; // Boolean : capture if the mouse is pressed
setStopUpdate = function (stop) {
stopUpdate = stop;
};
$('select[name="Player.Screen"] > option').each(function (i) {
$(this).text(i + 1 + ' - ' + $(this).text());
});
});
/*** INITIALISATION ***/
......@@ -371,16 +308,10 @@ var mouseDown; // Boolean : capture if the mouse is pressed
mouseDown = false;
panel1Default = -1;
// nameExclude = input not being updated (most likely user is on it)
getSettings = function (nameExclude) {
getSettings = function () {
var promise = $.Deferred();
$.ajax({ url: 'admin/settings' }).done(function (data) {
settings = data;
if(settings.Online.Stats === undefined) {
window.callOnlineStatsModal();
}
playlistToAdd = data.Karaoke.Private ? 'current' : 'public';
$.ajax({ url: 'public/playlists/' + playlistToAdd, }).done(function (data) {
......
......@@ -183,7 +183,7 @@ var plData;
$('.changePseudo').click( function() {
if(logInfos.token && !showedLoginAfter401) {
showProfil();
window.callProfileModal();
} else {
window.callLoginModal(scope);
}
......@@ -645,13 +645,6 @@ var plData;
});
/* login stuff END */
/* profil stuff */
showProfil = function() {
window.callProfileModal();
};
/* profil stuff END */
/* prevent the virtual keyboard popup when on touchscreen by not focusing the search input */
if(isTouchScreen) {
$('#progressBarColor').addClass('cssTransition');
......@@ -1219,10 +1212,8 @@ var plData;
/**
* refresh the player infos
* @param {Function} callback - function to call at the end of the refresh
* @param {anything} param1 - param to give to this function
*/
refreshPlayerInfos = function (data, callback, param1) {
refreshPlayerInfos = function (data) {
if (oldState != data && logInfos.username) {
var newWidth = $('#karaInfo').width() * parseInt(10000 * ( data.timePosition + refreshTime/1000) / $('#karaInfo').attr('length')) / 10000 + 'px';
......@@ -1234,15 +1225,12 @@ var plData;
status = data.status === 'stop' ? 'stop' : data.playerStatus;
switch (status) {
case 'play':
$('#status').attr('name','pause');
$('#progressBarColor').addClass('cssTransform');
break;
case 'pause':
$('#status').attr('name', 'play');
$('#progressBarColor').removeClass('cssTransform');
break;
case 'stop':
$('#status').attr('name', 'play');
$('#progressBarColor').removeClass('cssTransform');
break;
default:
......@@ -1291,38 +1279,8 @@ var plData;
});
}
}
if (data.showSubs != oldState.showSubs) {
if (data.showSubs) {
$('#showSubs').attr('name','hideSubs');
} else {
$('#showSubs').attr('name','showSubs');
}
}
if (data.muteStatus != oldState.muteStatus) {
if (!data.muteStatus) {
$('#mutestatus').attr('name','mute');
} else {
$('#mutestatus').attr('name','unmute');
}
}
if (data.onTop != oldState.onTop) {
$('input[name="Player.StayOnTop"]').bootstrapSwitch('state', data.onTop, true);
}
if (data.fullscreen != oldState.fullscreen) {
$('input[name="Player.FullScreen"]').bootstrapSwitch('state', data.fullscreen, true);
}
if (data.volume != oldState.volume) {
var val = data.volume, base = 100, pow = .76;
val = val / base;
val = base * Math.pow(val, 1/pow);
val = parseInt(val);
$('input[name="setVolume"]').val(val);
}
oldState = data;
if (callback && typeof callback === 'function' && typeof param1 != 'undefined') {
callback(param1);
}
}
};
......@@ -1623,13 +1581,13 @@ var plData;
* Init bootstrapSwitchs
*/
initSwitchs = function () {
$('input[switch="onoff"],[name="Karaoke.Private"],[name="kara_panel"],[name="lyrics"]').bootstrapSwitch('destroy', true);
$('input[switch="onoff"],[name="kara_panel"],[name="lyrics"]').bootstrapSwitch('destroy', true);
$('input[switch="onoff"]').bootstrapSwitch({
wrapperClass: 'btn btn-default',
'data-size': 'normal'
});
$('[name="Karaoke.Private"],[name="kara_panel"],[name="lyrics"]').bootstrapSwitch({
$('[name="kara_panel"],[name="lyrics"]').bootstrapSwitch({
'wrapperClass': 'btn',
'data-size': 'large',
'labelWidth': '15',
......@@ -1644,36 +1602,7 @@ var plData;
$container.attr('introLabel', introLabel).attr('introStep', introStep);
}
});
/* init selects & switchs */
if(scope === 'admin') {
$('[name="kara_panel"]').on('switchChange.bootstrapSwitch', function (event, state) {
if (state) {
$('#playlist').show();
$('#manage').hide();
} else {
$('#playlist').hide();
$('#manage').show();
if(introManager && introManager._currentStep) {
introManager.nextStep();
}
}
});
$('input[name="Karaoke.Private"]').on('switchChange.bootstrapSwitch', function () {
const value = $(this).val() === 'on' ? true : false;
$.ajax({
type: 'PUT',
url: 'admin/settings',
contentType: 'application/json',
dataType: 'json',
data: JSON.stringify({ 'setting': {'Karaoke': {'Private': value}} })
});
});
}
/* set the right value for switchs */
$('input[type="checkbox"],[switch="onoff"]').on('switchChange.bootstrapSwitch', function () {
$(this).val($(this).is(':checked') ? 'true' : 'false');
......
<div id="header" class="header" introStep="6" introLabel="lecteur" introTooltipClass="_introBottom">
<div class="dropdown btn btn-default btn-dark pull-right" id="manageButton">
<button class="btn btn-dark pull-right dropdown-toggle klogo" type="button" id="dropdownMenu1" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
</button>
<ul class="dropdown-menu" aria-labelledby="dropdownMenu1">
<li title="{{i18n "ACCOUNT"}}" action="account" class="btn btn-default btn-dark">
<i class="glyphicon glyphicon-user"></i></li>
<li title="{{i18n "LOGOUT"}}" action="logout" class="btn btn-default btn-dark">
<i class="glyphicon glyphicon-log-out"></i></li>
<li title="{{i18n "SHUTDOWN"}}" action="poweroff" class="btn btn-default btn-dark">
<i class="glyphicon glyphicon-off"></i></li>
</ul>
</div>
<a title="{{i18n "MUTE_UNMUTE"}}" action="command" id="mutestatus" name="mute" class="btn btn-default btn-dark pull-right">
<i class="glyphicon glyphicon-volume-up unmute"></i>
<i class="glyphicon glyphicon-volume-off mute"></i>
<input title='{{i18n "VOLUME_LEVEL"}}' action="command" name="setVolume" id="volume" value="100" type="range" />
</a>
<button title="{{i18n "SHOW_HIDE_SUBS"}}" action="command" id="showSubs" name="hideSubs" class="btn btn-default btn-dark pull-right">
<i class="glyphicon glyphicon-subtitles hideSubs"></i>
<i class="glyphicon glyphicon glyphicon-question-sign showSubs"></i>
</button>
<button title="{{i18n "MESSAGE"}}" id="adminMessage" class="btn btn-dark pull-right" style="border-left-width: 0px"><i class="glyphicon glyphicon-comment"></i></button>
<div class="pull-left btn-group switchs" style="width: 129px; max-width: 12.4vw;">
<input title='{{i18n "SWITCH_PRIVATE"}}' data-introStep="7" data-introLabel="mode" type="checkbox" checked name="Karaoke.Private" class="btn pull-left" data-on-text="<i class='glyphicon glyphicon-eye-close'></i> {{i18n "PRIVATE"}}"
data-off-text="<i class='glyphicon glyphicon-eye-open'></i> {{i18n "PUBLIC"}}" data-size="large"/>
<input title='{{i18n "SWITCH_OPTIONS"}}' data-introStep="13" data-introLabel="settings" type="checkbox" checked name="kara_panel" class="btn pull-left" data-off-text="<i class='glyphicon glyphicon-cog'></i> {{i18n "OPTIONS"}}"
data-on-text="<i class='glyphicon glyphicon glyphicon-music'></i> {{i18n "CL_PLAYLISTS"}}" data-size="large"/>
</div>
<div class="pull-left btn-group">
<button title="{{i18n "STOP_AFTER"}}" action="command" id="stopAfter" name="stopAfter" class="btn btn-danger-low" style="width: 50px;">
<i class="glyphicon glyphicon-stop"></i><i class="glyphicon glyphicon-time secondaryIcon"></i></button>
<button title="{{i18n "STOP_NOW"}}" action="command" id="stopNow" name="stopNow" class="btn btn-danger"> <i class="glyphicon glyphicon-stop"></i></button>
<button title="{{i18n "REWIND"}}" action="command" id="goTo" name="goTo" value="0" class="btn btn-dark"><i class="glyphicon glyphicon-fast-backward"></i></button>
</div>
<div class="btn-group centerBtns">
<button title="{{i18n "REWIND"}}" action="command" id="prev" name="prev" class="btn btn-default"><i class="glyphicon glyphicon-chevron-left"></i></button>
<button title="{{i18n "PLAY_PAUSE"}}" action="command" id="status" name="play" class="btn btn-primary"><i class="glyphicon glyphicon-play play"></i><i class="glyphicon glyphicon-pause pause"></i></button>
<button title="{{i18n "NEXT_SONG"}}" action="command" id="skip" name="skip" class="btn btn-default"><i class="glyphicon glyphicon-chevron-right"></i></button>
<button class="btn btn-default hidden"></button>
</div>
</div>
<div id="progressBar" class="underHeader">
<div id="karaInfo" idKara="-1" ondragstart="return false" draggable="false"><span>{{i18n "KARA_PAUSED_WAITING"}}<span></div>
<div id="progressBarColor" class="cssTransform"></div>
......
......@@ -17,6 +17,7 @@
<body scope="admin">
<div id="root"></div>
<div id="adminHeader"></div>
{{{ body }}}
<a id="downloadAnchorElem"></a>
</body>
......@@ -45,4 +46,5 @@
<script src="/ressources/js/karaokemugen.js"></script>
<script src="/ressources/js/tools.js"></script>
<script src="/ressources/js/admin.js"></script>
<script>window.adminHeader();</script>
</html>
\ No newline at end of file
......@@ -21,5 +21,32 @@
<a id="downloadAnchorElem"></a>
<div class="toastMessageContainer"></div>
</body>
<script>
var query = {{{ query }}};
var scope = 'welcome';
var appFirstRun = {{ appFirstRun }} == true;
var webappMode = 2;
welcomeScreen = true;
</script>
<script src="/ressources/vendors/js/jquery-3.2.1.min.js"></script>
<script src="/ressources/vendors/js/select2.min.js"></script>
<script src="/ressources/vendors/js/bootstrap.min.js"></script>
<script src="/ressources/vendors/js/i18n.js"></script>
<script src="/ressources/vendors/js/assignIE.js"></script>
<script src="/ressources/vendors/js/bootstrap-switch.min.js"></script>
<script src="/ressources/vendors/js/jquery-ui.min.js"></script>
<script src="/ressources/vendors/js/jquery.ui.touch-punch.min.js"></script>
<script src="/ressources/vendors/js/hammer.min.js"></script>
<script src="/ressources/vendors/js/hammer-time.min.js"></script>
<script src="/ressources/vendors/js/velocity.min.js"></script>
<script src="/ressources/vendors/js/perfect-scrollbar.jquery.min.js"></script>
<script src="/ressources/vendors/js/intro.js"></script>
<script src="/ressources/vendors/js/sprintf.min.js"></script>
<script src="/build/app.js"></script>
<script src="/ressources/js/karaokemugen.js"></script>
<script src="/ressources/js/admin.js"></script>
<script src="/ressources/js/tools.js"></script>
<script>window.welcomePage();</script>
</html>
\ No newline at end of file
<!-- Js -->
<script>
var query = {{{ query }}};
var scope = 'welcome';
var appFirstRun = {{ appFirstRun }} == true;
var webappMode = 2;
welcomeScreen = true;
</script>
<script src="/ressources/vendors/js/jquery-3.2.1.min.js"></script>
<script src="/ressources/vendors/js/select2.min.js"></script>
<script src="/ressources/vendors/js/bootstrap.min.js"></script>
<script src="/ressources/vendors/js/i18n.js"></script>
<script src="/ressources/vendors/js/assignIE.js"></script>
<script src="/ressources/vendors/js/bootstrap-switch.min.js"></script>
<script src="/ressources/vendors/js/jquery-ui.min.js"></script>
<script src="/ressources/vendors/js/jquery.ui.touch-punch.min.js"></script>
<script src="/ressources/vendors/js/hammer.min.js"></script>
<script src="/ressources/vendors/js/hammer-time.min.js"></script>
<script src="/ressources/vendors/js/velocity.min.js"></script>
<script src="/ressources/vendors/js/perfect-scrollbar.jquery.min.js"></script>
<script src="/ressources/vendors/js/intro.js"></script>
<script src="/ressources/vendors/js/sprintf.min.js"></script>
<script src="/build/app.js"></script>
<script src="/ressources/js/karaokemugen.js"></script>
<script src="/ressources/js/admin.js"></script>
<script src="/ressources/js/tools.js"></script>
<script>window.welcomePage();</script>
\ No newline at end of file
......@@ -14,5 +14,4 @@
@import './styles/admin.scss';
@import './styles/main.scss';
@import './styles/public.scss';
@import './styles/Switch.scss';
@import './styles/Autocomplete.scss';
\ No newline at end of file
......@@ -15,6 +15,7 @@ import axios from 'axios';
import WelcomePage from './components/WelcomePage';
import KaraDetail from './components/karas/KaraDetail';
import KaraList from './components/karas/KaraList';
import AdminHeader from './components/AdminHeader';
const Loader = () => (
<div>loading...</div>
......@@ -77,3 +78,7 @@ window.buildKaraList = (data, scope, idPlaylist, flagPublic, filter, side) => {
<KaraList data={data} scope={scope} idPlaylist={idPlaylist} flagPublic={flagPublic} filter={filter} gitlabEnabled={settings.config.Gitlab.Enabled} side={side}/>
</Suspense>, document.getElementById('playlist' + side));
}
window.adminHeader = () => {
ReactDOM.render(<Suspense fallback={<Loader />}><AdminHeader config={settings.config} profileModal={callProfileModal} callModal={callModal}/></Suspense>, document.getElementById('adminHeader'));
}
\ No newline at end of file
This diff is collapsed.
import React, { Component } from "react";
import '../styles/RadioButton.scss';
class RadioButton extends Component {
constructor(props) {
super(props);
}
render() {
return (
<div className="radiobutton-ui">
{
this.props.buttons.map(function(item,i){
var style = {};
if(item.active && item.activeColor)
style.backgroundColor = item.activeColor;
return (
<button
key={i}
type="button"
className={item.active ? 'active':''}
style={style}
onClick={item.onClick}
>
{item.label}
</button>
)
})
}
</div>
);
}
}
export default RadioButton;
\ No newline at end of file
import React, { Component } from "react";
import '../styles/Switch.scss';
class Switch extends Component {
constructor(props) {
......
......@@ -10,7 +10,7 @@ class KaraLine extends Component {
}
getActionsDiv() {
var addKaraButton = (<button title={this.props.t('TOOLTIP_ADDKARA') + (scope == 'admin' ? ' - ' + t('TOOLTIP_ADDKARA_ADMIN') : '')}
var addKaraButton = (<button title={this.props.t('TOOLTIP_ADDKARA') + (scope == 'admin' ? ' - ' + this.props.t('TOOLTIP_ADDKARA_ADMIN') : '')}
name="addKara" className="btn btn-sm btn-action"></button>);
if (this.props.scope === 'admin' && (this.props.idPlaylist >= 0 || this.props.idPlaylist === -3)) {
// Admin et playlist standard ou Whitelist
......
import React, { Component } from "react";
import { withTranslation } from "react-i18next";
import KaraLine from "./KaraLine";
class KaraList extends Component {
......@@ -7,6 +8,7 @@ class KaraList extends Component {
}
render() {
const t = this.props.t;
var data = this.props.data;
var count = this.props.data.infos ? this.props.data.infos.count : 0;
var container = $('#panel' + this.props.side + ' .playlistContainer');
......@@ -30,4 +32,5 @@ class KaraList extends Component {
}
}
export default KaraList;
export default withTranslation()(KaraList);
\ No newline at end of file
import React, { Component } from "react";
import { withTranslation } from 'react-i18next';
import axios from 'axios';
import {expand} from '../toolsReact';
class OnlineStatsModal extends Component {
constructor(props) {
......@@ -8,14 +9,8 @@ class OnlineStatsModal extends Component {
this.onClick = this.onClick.bind(this);
}
expand(str, val) {
return str.split('.').reduceRight((acc, currentValue) => {
return { [currentValue]: acc };
}, val);
};
onClick(e) {
var data = this.expand("Online.Stats", eval(e.target.value));
var data = expand("Online.Stats", eval(e.target.value));
axios.put('/api/admin/settings', {
setting: JSON.stringify(data)
});
......
......@@ -4,6 +4,7 @@ import PlayerOptions from './PlayerOptions';
import KaraokeOptions from './KaraokeOptions';
import InterfaceOptions from './InterfaceOptions';
import axios from 'axios';
import {expand} from '../toolsReact';
require('babel-polyfill');
axios.defaults.headers.common['authorization'] = document.cookie.replace(/(?:(?:^|.*;\s*)mugenToken\s*\=\s*([^;]*).*$)|^.*$/, "$1");
......@@ -19,15 +20,9 @@ class Options extends Component {
this.saveSettings = this.saveSettings.bind(this);
}
expand (str, val) {
return str.split('.').reduceRight((acc, currentValue) => {
return { [currentValue]: acc };
}, val);
};
async saveSettings(event) {
const value = event.target.type === 'checkbox' ? event.target.checked : event.target.value;
var data = this.expand(event.target.id, eval(value));
var data = expand(event.target.id, eval(value));
const res = await axios.put('/api/admin/settings', {
setting: JSON.stringify(data)
});
......@@ -118,4 +113,4 @@ class Options extends Component {
};
}
export default withTranslation()(Options);
export default withTranslation()(Options);
\ No newline at end of file
......@@ -37,7 +37,7 @@ class PlayerOptions extends Component {
? this.state.displays.map((display, index) => (
<option key={index} value={index} >
{" "}
({display.resolutionx}x{display.resolutiony}) {display.model}
{index+1} - ({display.resolutionx}x{display.resolutiony}) {display.model}
</option>
))
: null;
......
......@@ -47,6 +47,14 @@ export function is_touch_device() {
};
export function expand (str, val) {
return str.split('.').reduceRight((acc, currentValue) => {
return { [currentValue]: acc };
}, val);
};
/* format seconds to Hour Minute Second */
export function secondsTimeSpanToHMS (s, format) {
var d = Math.floor(s/(3600 * 24));
......
.radiobutton-ui {
display: flex;
flex-direction: row;
button {
display: block;
flex:1;
padding: 0;
margin: 1px;
border: none;
background: #575757;
color: #aaa;
outline:none;
transition: color ease .5s, background ease .5s;
&.active {
background: #37679a;
color: #FFF;
}
}
}
\ No newline at end of file
......@@ -40,6 +40,12 @@ body[scope="admin"] {
}
.header > .btn-group.switchs {
height: calc(100% - 0px);
width: 10em;
display: flex;
flex-direction: column;
> * {
flex:1;
}
}
.header, .header .btn, #manageButton li, .header input[type=range] {
max-height: 7.4vw;
......@@ -164,6 +170,13 @@ body[scope="admin"] {
display: none;
}
.btn-left {
height: 2em;
background-color: #f4f4f4;
color: #1E2124;
border-color: #9e9e9e;
}
.btn-stop {
height: 100%;
}
......
export const KARAS_LOAD_LOCAL = 'KARAS_LOAD_LOCAL';
export const KARAS_FILTER_LOCAL = 'KARAS_FILTER_LOCAL';
export const KARAS_DELETE_KARA = 'KARAS_DELETE_KARA';
export const KARAS_FILTER_ONLINE = 'KARAS_FILTER_ONLINE';
export const KARAS_LOAD_ONLINE = 'KARAS_LOAD_RECENT_ONLINE';
export const KARAS_LOAD_DOWNLOAD_QUEUE = 'KARAS_LOAD_DOWNLOAD_QUEUE';
export const KARAS_SET_IS_SEARCHING = 'KARAS_SET_IS_SEARCHING';
export const KARAS_DOWNLOAD_ADD_TO_QUEUE = 'KARAS_DOWNLOAD_ADD_TO_QUEUE';
export const KARAS_DOWNLOAD_PROGRESS_UPDATE = 'KARAS_DOWNLOAD_PROGRESS_UPDATE';
export const KARAS_DOWNLOAD_START = 'KARAS_DOWNLOAD_START';
export const KARAS_DOWNLOAD_PAUSE = 'KARAS_DOWNLOAD_PAUSE';
// Runs a filter against the karas, {} || null will get all karas
export function filterLocalKaras(filter) {
return {
type: KARAS_FILTER_LOCAL,
payload: filter
};
}
// Action to put the localKaras into the redux store
export function loadLocalKaras(localKaras) {
return {
type: KARAS_LOAD_LOCAL,
payload: localKaras
};
}
export function deleteKara(kid) {
return {
type: KARAS_DELETE_KARA,
payload: kid
};
}
// Fetch recent karas from kara.moe. Defaults to recent if no filter is applied (for now)
export function filterOnlineKaras(filter) {
return {
type: KARAS_FILTER_ONLINE,
payload: filter
};
}
// Action to put the onlineKaras into the redux store
export function loadOnlineKaras(onlineKaras) {
return {
type: KARAS_LOAD_ONLINE,
payload: onlineKaras
};
}
export function setIsSearching(isSearching) {
return {
type: KARAS_SET_IS_SEARCHING,
payload: isSearching
};
}
export function downloadSong(kid) {
return {
type: KARAS_DOWNLOAD_ADD_TO_QUEUE,
payload: kid
};
}
export function downloadStart() {
return {
type: KARAS_DOWNLOAD_START,
payload: null
};
}
export function downloadPause() {
return {
type: KARAS_DOWNLOAD_PAUSE,
payload: null
};
}
\ No newline at end of file
import axios from 'axios';
/**
* I'll be naming api requests with the first word being the http method.
* ex. getSomeThing || postSomething || ...etc
*/
// GET karas with/without filter.
export async function getLocalKaras() {
try {
const res = await axios.get('/api/system/karas');
return res.data.content;
} catch (e) {
console.log('Error from /api/local.js:getLocalKaras()');
throw e;
}
}
// START karas download queue
export async function putToDownloadQueueStart() {
try {
const res = await axios.put('/api/system/downloads/start');
return res.data;
} catch (e) {
console.log('Error from /api/local.js:putToDownloadQueueStart');
throw e;
}
}
// PAUSE karas download queue
export async function putToDownloadQueuePause() {
try {
const res = await axios.put('/api/system/downloads/pause');
return res.data;
} catch (e) {
console.log('Error from /api/local.js:putToDownloadQueuePause');
throw e;
}
}
// GET karas download queue
export async function getDownloadQueue() {
try {
const res = await axios.get('/api/system/downloads');
return res.data;
} catch (e) {
console.log('Error from /api/local.js:getDownloadQueue');
throw e;
}
}
// POST (add) items to download queue
export async function postToDownloadQueue(repo = 'kara.moe', downloads) {
try {
const dl = {
repository: repo,
downloads
};
await axios.post('/api/system/downloads', dl);
} catch (e) {
console.log('Error from /api/local.js:postToDownloadQueue');
throw e;
}
}
export async function deleteKaraByLocalId(karaId) {
try {
const response = await axios.delete(`/api/system/karas/${karaId}`);
console.log(response);
return response.status === 200;
} catch (e) {
console.log(e);
console.error('Error from /api/local.js:deleteKaraByLocalId');
throw e;
}
}
\ No newline at end of file
import { createStore, combineReducers, applyMiddleware, compose } from 'redux';
import { routerReducer, routerMiddleware } from 'react-router-redux';
import createHistory from 'history/createBrowserHistory';
import navigation from './reducers/navigation';
import auth from './reducers/auth';
//import karas from './reducers/karas';
const initialState = {};
export const history = createHistory();
const middleware = [routerMiddleware(history)];
// If devtools is present use it's compose instead of redux's compose; Does the same thing
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
export default () => {
const reducers = combineReducers({
navigation,
auth,
//karas,
router: routerReducer
});
const store = createStore(
reducers,
initialState, // default state
composeEnhancers(applyMiddleware(...middleware))
);
return store;
};
import React, {Component} from 'react';
import axios from 'axios';
import {connect} from 'react-redux';
import {Row, Col, Icon, Layout, Table, Input, Button} from 'antd';
import {loading, errorMessage, warnMessage} from '../../actions/navigation';
import openSocket from 'socket.io-client';
import { getLocalKaras, postToDownloadQueue, putToDownloadQueueStart, putToDownloadQueuePause } from '../../api/local';
class KaraDownload extends Component {
constructor(props) {
super(props);
this.state = {
karas_local: [],
karas_online: [],
karas_online_count: 0,
karas_queue: [],
active_download: null,
kara: {},
currentPage: parseInt(localStorage.getItem('karaDownloadPage')) || 1,
currentPageSize: parseInt(localStorage.getItem('karaDownloadPageSize')) || 100,
filter: localStorage.getItem('karaDownloadFilter') || ''
};
}
componentDidMount() {
const socket = openSocket('http://localhost:1337');
socket.on('downloadBatchProgress', (data) => {
});
socket.on('downloadProgress', (data) => {
let active_download = null;
if(this.state.karas_online) {
this.state.karas_online.forEach((kara,i) => {
if(kara.name == data.id) {
let remain = parseInt(data.total) - parseInt(data.value);
if(remain>0) {
active_download = {
index: i,
progress:Math.round(100 * parseInt(data.value) / parseInt(data.total)),
};
}
}
});
if(JSON.stringify(this.state.active_download) != JSON.stringify(active_download))
this.setState({active_download:active_download});
}
});
this.api_get_local_karas();
this.api_get_online_karas();
this.api_read_kara_queue();
setInterval(this.api_get_local_karas.bind(this),2000);
setInterval(this.api_read_kara_queue.bind(this),1000);
}
changeFilter(event) {
this.setState({filter: event.target.value}, () => {
localStorage.setItem('karaDownloadFilter', this.state.filter);
});
}
downloadKara(kara) {
let downloadObject = {};
downloadObject.kid = kara.kid;
downloadObject.mediafile = kara.mediafile;
downloadObject.subfile = kara.subfile;
downloadObject.karafile = kara.karafile;
downloadObject.seriefiles = kara.seriefiles;
downloadObject.size = kara.mediasize;
downloadObject.name = kara.name;
postToDownloadQueue('kara.moe', [downloadObject]);
this.api_read_kara_queue();
}
downloadAll() {
this.props.loading(true);
axios.get(`/api/system/karas?filter=${this.state.filter}&instance=kara.moe`)
.then(res => {
let karas = res.data.content;
karas.forEach((kara) => {
kara.name = kara.subfile.replace('.ass', '');
this.downloadKara(kara);
});
this.props.loading(false);
})
.catch(err => {
this.props.loading(false);
this.props.errorMessage(`${err.status}: ${err.statusText}. ${err.data}`);
});
}
async api_get_local_karas() {
this.setState({karas_local: await getLocalKaras()});
}
api_get_online_karas() {
this.props.loading(true);
var p = Math.max(0,this.state.currentPage - 1);
var psz = this.state.currentPageSize;
var pfrom = p*psz;
axios.get(`/api/system/karas?filter=${this.state.filter}&from=${pfrom}&size=${psz}&instance=kara.moe`)
.then(res => {
let karas = res.data.content;
karas = karas.map((kara) => {
kara.name = kara.subfile.replace('.ass', '');
return kara;
});
this.props.loading(false);
this.setState({
karas_online: karas,
karas_online_count: res.data.infos.count || 0,
});
})
.catch(err => {
this.props.loading(false);
this.props.errorMessage(`${err.status}: ${err.statusText}. ${err.data}`);
});
}
async api_read_kara_queue() {
try {
const res = await axios.get('/api/system/downloads');
this.setState({karas_queue: res.data});
} catch (e) {
console.log('Error KaraDownload.js in api_read_kara_queue');
throw e;
}
}
handleTableChange = (pagination, filters, sorter) => {
this.setState({
currentPage: pagination.current,
currentPageSize: pagination.pageSize,
});
localStorage.setItem('karaDownloadPage',pagination.current);
localStorage.setItem('karaDownloadPageSize',pagination.pageSize);
setTimeout(this.api_get_online_karas.bind(this),10);
}
render() {
return (
<Layout.Content style={{ padding: '25px 50px', textAlign: 'center' }}>
<Layout>
<Layout.Header>
<Row type="flex" justify="space-between">
<Col span={20}>
<Input.Search
placeholder='Search filter'
value={this.state.filter}
onChange={event => this.changeFilter(event)}
enterButton="Search"
onSearch={this.api_get_online_karas.bind(this)}
/>
</Col>
<Col>
<Button type="primary" key="queueStart" onClick={putToDownloadQueueStart}>Start</Button>
&nbsp;
<Button type="primary" key="queuePause" onClick={putToDownloadQueuePause}>Pause</Button>
</Col>
</Row>
</Layout.Header>
<Layout.Content>
<Table
onChange={this.handleTableChange}
dataSource={this.state.karas_online}
columns={this.columns}
rowKey='kid'
pagination={{
position:'both',
current: this.state.currentPage || 0,
defaultPageSize: this.state.currentPageSize,
pageSize: this.state.currentPageSize,
pageSizeOptions: ['10','25','50','100','500'],
showTotal: (total, range) => {
const to = range[1];
const from = range[0];
return `Showing ${from}-${to} of ${total} songs`;
},
total: this.state.karas_online_count,
showSizeChanger: true,
showQuickJumper: true,
}}
/>
</Layout.Content>
</Layout>
</Layout.Content>
);
}
is_local_kara(kara) {
return this.state.karas_local.find(item => item.kid == kara.kid);
}
is_queued_kara(kara) {
return this.state.karas_queue.find(item => item.name == kara.name);
}
columns = [{
title: 'Language(s)',
dataIndex: 'languages',
key: 'languages',
render: languages => {
const ret = languages ? languages.map(e => {
return e.name;
}) : [];
return ret.join(', ').toUpperCase();
}
}, {
title: 'Series/Singer',
dataIndex: 'serie',
key: 'serie',
render: (serie, record) => {
const singers = record.singers ? record.singers.map(e => {
return e.name;
}) : [];
return serie || singers.join(', ');
}
}, {
title: 'Type',
dataIndex: 'songtype',
key: 'songtype',
render: (songtypes, record) => {
const types = songtypes ? songtypes.map(e => {
return e.name;
}) : [];
const songorder = record.songorder || '';
return types.join(', ').replace('TYPE_','') + ' ' + songorder || '';
}
}, {
title: 'Title',
dataIndex: 'title',
key: 'title',
render: (title, record) => {
if(this.is_local_kara(record.kid))
return <strong>{title}</strong>;
return <span>{title}</span>;
}
}, {
title: <span><button title="Download all retrieved karas at once" type="button" onClick={this.downloadAll.bind(this)}><Icon type='download'/></button> Download</span>,
key: 'download',
render: (text, record) => {
var button = null;
if(this.is_local_kara(record))
button = <button disabled type="button"><Icon type='check-circle' theme="twoTone" twoToneColor="#52c41a"/></button>;
else {
let queue = this.is_queued_kara(record);
if(queue) {
if(queue.status=='DL_RUNNING')
button = <span><button disabled type="button"><Icon type="sync" spin /></button> {this.state.active_download ? this.state.active_download.progress:null}%</span>;
else if(queue.status=='DL_PLANNED')
button = <button disabled type="button"><Icon type='clock-circle' theme="twoTone" twoToneColor="#dc4e41"/></button>;
else if(queue.status=='DL_DONE') // done but not in local -> try again dude
button = <span><button disabled type="button"><Icon type='check-circle' theme="twoTone" twoToneColor="#4989f3"/></button></span>;
} else
button = <button type="button" onClick={this.downloadKara.bind(this,record)}><Icon type='download'/></button>;
}
return <span>{button} {Math.round(record.mediasize/(1024*1024),1)}Mb</span>;
}
}];
}
const mapStateToProps = (state) => ({
loadingActive: state.navigation.loading
});
const mapDispatchToProps = (dispatch) => ({
loading: (active) => dispatch(loading(active)),
errorMessage: (message) => dispatch(errorMessage(message)),
warnMessage: (message) => dispatch(warnMessage(message))
});
export default connect(mapStateToProps, mapDispatchToProps)(KaraDownload);
import {
KARAS_LOAD_LOCAL,
KARAS_FILTER_LOCAL,
KARAS_LOAD_ONLINE,
KARAS_FILTER_ONLINE,
KARAS_SET_IS_SEARCHING,
KARAS_LOAD_DOWNLOAD_QUEUE,
KARAS_DOWNLOAD_PROGRESS_UPDATE,
KARAS_DOWNLOAD_START,
KARAS_DOWNLOAD_PAUSE
} from '../actions/karas';
const defaultState = {
localKaras: [],
onlineKaras: [],
downloadQueue: [],
downloadState: 'pause',
isWatchingDownloadQueue: false,
isSearching: false
};
// Selectors
export const fetchLocalKaras = state => {
return state.karas.localKaras;
};
export const fetchLocalKara = (state, kid) => {
const localKaras = fetchLocalKaras(state);
return localKaras.find(k => k.kid === kid);
};
export const fetchDownloadQueue = state => {
return state.karas.downloadQueue;
};
export default function(state = defaultState, action) {
switch (action.type) {
case KARAS_LOAD_LOCAL:
return {
...state,
localKaras: action.payload
};
case KARAS_LOAD_ONLINE:
return {
...state,
onlineKaras: action.payload
};
case KARAS_LOAD_DOWNLOAD_QUEUE:
return {
...state,
downloadQueue: action.payload
};
case KARAS_DOWNLOAD_START:
return {
...state,
downloadState: 'start'
};
case KARAS_DOWNLOAD_PAUSE:
return {
...state,
downloadState: 'pause'
};
case KARAS_SET_IS_SEARCHING:
return {
...state,
isSearching: action.payload
};
case KARAS_DOWNLOAD_PROGRESS_UPDATE:
// Would have been better if it was a more exact update to the store
return {
...state,
downloadQueue: action.payload
};
/**
* Some actions don't yet need to change the state but might,
* example filters may or may not be stored in the store.
* So for now it'll do the same as default:
*/
// case KARAS_FILTER_LOCAL:
// case KARAS_FILTER_ONLINE:
default:
return state;
}
}
**Faire une recherche avec des filtres**
mots clé + tag + série ... (comme sur le kara explorer en gros)
**MAJ sa base de kara**
expl : bouton permettant de télécharger les derniers karas
+ ceux qui ont un date modif différent
**Pouvoir retirer un kara de la file d'attente**
+ Ou bien la purger intégralement
*Etablibir une blacklist de kara à ne jamais proposer au téléchargement*
This diff is collapsed.
import {db, transaction} from './database';
const sql = require('./sql/download');
export async function insertDownloads(downloads) {
const dls = downloads.map(dl => [
dl.name,
dl.urls,
dl.size,
'DL_PLANNED',
dl.uuid
]);
return await transaction([{sql: sql.insertDownload, params: dls}]);
}
export async function selectDownloads() {
const dls = await db().query(sql.selectDownloads);
return dls.rows;
}
export async function selectPendingDownloads() {
const dls = await db().query(sql.selectPendingDownloads);
return dls.rows;
}
export async function initDownloads() {
await db().query(sql.updateRunningDownloads);
await db().query(sql.deleteDoneFailedDownloads);
}
export async function selectDownload(id) {
const dl = await db().query(sql.selectDownload, [id]);
return dl.rows[0];
}
export async function deleteDownload(id) {
return await db().query(sql.deleteDownload, [id]);
}
export async function updateDownload(uuid, status) {
return await db().query(sql.updateDownloadStatus, [
status,
uuid
]);
}
export async function emptyDownload() {
return await db().query(sql.emptyDownload);
}
export async function selectDownloadBLC() {
const res = await db().query(sql.selectDownloadBLC);
return res.rows;
}
export async function truncateDownloadBLC() {
return await db().query(sql.truncateDownloadBLC);
}
export async function deleteDownloadBLC(id) {
return await db().query(sql.deleteDownloadBLC, [id]);
}
export async function insertDownloadBLC(type, value) {
return await db().query(sql.insertDownloadBLC, [type, value]);
}
export async function updateDownloadBLC(id, type, value) {
return await db().query(sql.updateDownloadBLC, [id, type, value]);
}
\ No newline at end of file
import {db} from './database';
const sql = require('./sql/repo');
export async function selectRepos() {
const repos = await db().query(sql.selectRepos);
return repos.rows;
}
export const selectDownloads = `
SELECT name,
urls,
size,
status,
pk_uuid,
started_at
FROM download
ORDER BY started_at DESC
`;
export const selectPendingDownloads = `
SELECT name,
urls,
size,
status,
pk_uuid,
started_at
FROM download
WHERE status = 'DL_PLANNED'
ORDER BY started_at DESC
`;
export const selectDownload = `
SELECT name,
urls,
size,
status,
pk_uuid,
started_at
FROM download
WHERE pk_uuid = $1
`;
export const updateRunningDownloads = `
UPDATE download
SET status = 'DL_PLANNED'
WHERE status = 'DL_RUNNING'
`;
export const deleteDoneFailedDownloads = `
DELETE FROM download
WHERE status = 'DL_DONE' OR status = 'DL_FAILED'
`;
export const insertDownload = `
INSERT INTO download(
name,
urls,
size,
status,
pk_uuid
) VALUES(
$1,
$2,
$3,
$4,
$5)
`;
export const updateDownloadStatus = `
UPDATE download
SET status = $1
WHERE pk_uuid = $2
`;
export const deleteDownload = `
DELETE FROM download
WHERE pk_uuid = $1
`;
export const emptyDownload = 'TRUNCATE download CASCADE';
export const selectDownloadBLC = `
SELECT
pk_id_dl_blcriteria AS dlBLC_id,
type,
value
FROM download_blacklist_criteria
`;
export const deleteDownloadBLC = `
DELETE FROM download_blacklist_criteria
WHERE pk_id_dl_blcriteria = $1;
`;
export const truncateDownloadBLC = `
TRUNCATE download_blacklist_criteria RESTART IDENTITY;
`;
export const insertDownloadBLC = `
INSERT INTO download_blacklist_criteria(
type,
value
) VALUES($1, $2)
`;
export const updateDownloadBLC = `
UPDATE download_blacklist_criteria
SET type = $2, value = $3
WHERE pk_id_dl_blcriteria = $1
`;
\ No newline at end of file
export const selectRepos = `
SELECT
pk_repo_name AS name,
last_downloaded_at
FROM repo
`;
\ No newline at end of file