114 Commits
Author SHA1 Message Date
Gregorio 8bf6ffe97f aaa 2019-03-04 14:26:16 +01:00
Gregorio 82b7997da8 Merge branch 'layout1' of https://bitbucket.org/sco44/tecnologiabanana into layout1 2019-03-04 14:19:19 +01:00
Gregorio 4d5e96a0e8 mocc a mammt 2019-03-04 14:19:03 +01:00
matteo 233088fe5b pozzino acchiapparti le intestinali brutte 2019-03-04 14:16:21 +01:00
matteo a0ccb675a0 ooo 2019-03-04 13:28:14 +01:00
matteo 546396c7cc try in wiki 2019-03-04 13:23:53 +01:00
matteo 597ca1cca0 wikipedia finito 2019-03-04 13:17:40 +01:00
matteo 9c35961a7a merged 2019-03-04 11:59:08 +01:00
matteo 82f3a01356 popglob 2019-03-04 11:45:06 +01:00
Gregorio 0bffa1094f aggiornata lista video, migliorata grafica 2019-03-04 11:28:40 +01:00
Gregorio a26b7fe72c Merge branch 'layout1' of https://bitbucket.org/sco44/tecnologiabanana into layout1 2019-03-03 22:38:42 +01:00
Gregorio a06540c03b sistemata la visualizzazione video pc/cell 2019-03-03 22:38:22 +01:00
matteo 6ba6caa03b Merge branch 'layout1' of https://bitbucket.org/sco44/tecnologiabanana into layout1 2019-03-03 21:59:18 +01:00
matteo 846e06d588 popglobass 2019-03-03 21:58:55 +01:00
Gregorio 0d28e4f5a6 Merge branch 'layout1' of https://bitbucket.org/sco44/tecnologiabanana into layout1 2019-03-03 20:55:47 +01:00
Gregorio 8475cb4ca8 sistemato popRelLoc.js 2019-03-03 20:54:26 +01:00
matteo 99c948f7b5 poplocrel 2019-03-03 20:54:14 +01:00
matteo d9d6138fd9 pusho1 2019-03-03 20:42:01 +01:00
matteo 12f375afed pusho 2019-03-03 20:16:01 +01:00
matteo 3a4a0e84c7 one at time 2019-03-03 18:55:00 +01:00
Gregorio bb67bb1697 jaja 2019-03-03 17:58:09 +01:00
Gregorio 8af517b965 RelPopLoc 2019-03-03 17:14:31 +01:00
matteo db8800965d videotrack 2019-03-03 16:32:50 +01:00
Gregorio 668b259adb Yojimbo 2019-03-03 16:30:39 +01:00
matteo cb35dfbe65 ta da 2019-03-03 15:09:54 +01:00
Gregorio d99f2fe057 èèèèè 2019-03-03 14:57:52 +01:00
Gregorio 41812bfb64 aggiunto popRelLoc 2019-03-02 22:36:11 +01:00
matteo 8ecd6ff76c black mamba 2019-03-02 18:00:48 +01:00
matteo 815a713412 oppela 2019-03-02 17:42:23 +01:00
Giampiero Galeazzi 10d54b51e6 reccomender similarity genere
-modificato VideoInfo per far funzionare il reccomender
2019-03-02 11:20:15 +01:00
Giampiero Galeazzi d6031d313b Merge remote-tracking branch 'origin/master' into layout1
# Conflicts:
#	src/AboutUs.js
#	src/VideoPlayer.js
#	src/reccomenders/Recent.js
#	src/reccomenders/fvitali.js
2019-03-01 15:41:34 +01:00
Giampiero Galeazzi 9032bc3f98 Merge remote-tracking branch 'origin/master' into layout1
# Conflicts:
#	src/AboutUs.js
#	src/VideoPlayer.js
#	src/reccomenders/Recent.js
#	src/reccomenders/fvitali.js
2019-03-01 15:40:07 +01:00
Giampiero Galeazzi 534d3c1b25 Merge branch 'layout1' of https://bitbucket.org/sco44/tecnologiabanana into layout1
# Conflicts:
#	src/Suggestion.js
2019-03-01 11:32:37 +01:00
Giampiero Galeazzi d847ab382d reccomender similarity genere 2019-03-01 11:30:06 +01:00
Giampiero Galeazzi 0e48971e1e reccomender similarity genere 2019-02-28 19:19:28 +01:00
Gregorio 5a88a201e7 aggiunto aboutus 2019-02-28 17:46:23 +01:00
Gregorio d844fab282 Pulizia About Us 2019-02-27 19:09:05 +01:00
Gregorio 1137e528e1 Abbozzo di AboutUs, aggiunto assets per foto 2019-02-27 19:07:14 +01:00
Gregorio 06800e51cd aggiunta funzione ordinamento di recent, ripulito recent 2019-02-26 12:04:24 +01:00
Gregorio 7fef149424 ripulito recent 2019-02-26 12:00:49 +01:00
Gregorio 340bb46c46 Recenti messi in ordine di data (funzione su videoplayer) 2019-02-25 18:45:48 +01:00
matteo 70be1d815c prod test 2019-02-20 00:19:59 +01:00
matteo 0340f45cd7 aggiunto similarity artist e poplocaleassoluta 2019-02-19 23:31:07 +01:00
matteo a4752a42da peppe spiderman 2019-02-19 17:25:01 +01:00
matteo aabf86e3d7 yoyoyo 2019-02-19 01:38:10 +01:00
matteo 5f86943496 migliorato lo stile 2019-02-19 01:16:25 +01:00
matteo 52d9ac8602 new colors 2019-02-18 23:09:09 +01:00
matteo 2466c9798d lastId non veniva aggiornato se l'url era /video/id/reccomender 2019-02-18 20:55:58 +01:00
Gregorio 0eaa8eb805 Ultimato fvitali 2019-02-18 20:38:37 +01:00
Gregorio 6e2a6c84df Sistemato fvitali 2019-02-18 19:33:35 +01:00
matteo 8057a99947 wikipedia e altro 2019-02-18 03:58:51 +01:00
matteo 61d113fd14 Merge branch 'master' of https://bitbucket.org/sco44/tecnologiabanana 2019-02-17 22:32:12 +01:00
Gregorio c28618dc51 Modificata top bar secondo la issue (primo commento) 2019-02-17 18:50:25 +01:00
matteo e162300121 merge 2019-02-17 16:43:34 +01:00
matteo 746c239f03 temporary 2019-02-17 16:41:29 +01:00
Giampiero Galeazzi bd55d431e8 Related giusto cosi 2019-02-17 16:35:18 +01:00
Giampiero Galeazzi 6c766c3c33 aggiustamenti vari da poco
-greg ghei
2019-02-17 16:26:40 +01:00
matteo f72ad098f9 Merge branch 'master' of https://bitbucket.org/sco44/tecnologiabanana 2019-02-17 16:20:35 +01:00
matteo 1d008dc41a eliminata areainfo/ ;; Suggestion e Wikipedia sono in src/ 2019-02-17 16:20:16 +01:00
Giampiero Galeazzi f0351615b7 Aggiornato reccomender Related 2019-02-17 16:18:34 +01:00
Giampiero Galeazzi 36932c5c3c Merge branch 'master' of https://bitbucket.org/sco44/tecnologiabanana 2019-02-17 12:32:33 +01:00
matteo 1c3a5b3409 maledetto il signore 2019-02-17 00:17:38 +01:00
matteo c01fe3daf7 pepsi 2019-02-16 22:55:51 +01:00
matteo e3970731be package.json package-lock.json freeze 2019-02-16 22:48:05 +01:00
matteo 108d4b72ee delirio 2019-02-16 22:41:22 +01:00
matteo c60f4b9323 tornati alla vecchia key, migliorato codice 2019-02-16 17:55:35 +01:00
Gregorio e32739efe2 Merge branch 'master' of https://bitbucket.org/sco44/tecnologiabanana 2019-02-14 17:13:12 +01:00
Gregorio 4e76a809e1 Recent Reccomender 2019-02-14 17:13:00 +01:00
matteo 18b0dbcc39 fixed errors and warnings 2019-02-14 17:01:45 +01:00
matteo dab303c617 Merge branch 'master' of https://bitbucket.org/sco44/tecnologiabanana 2019-02-14 15:04:00 +01:00
matteo 146e59fbed cambiata api key 2019-02-14 15:03:45 +01:00
Giampiero Galeazzi e624b3a7f7 termosifone 2019-02-14 12:02:04 +01:00
Gregorio 5ae00f9237 Tolti i NavDropown.item 2019-02-13 12:09:01 +01:00
matteo 407a6c18cc merged1 2019-02-12 18:59:33 +01:00
matteo 8ca65fed00 merged 2019-02-12 18:59:21 +01:00
Gregorio 4e906261be Iniziato Recent, Sistemato Navbar 2019-02-12 18:58:15 +01:00
matteo 4631fa7ec2 pomeriggio 12/02 2019-02-12 18:56:56 +01:00
Gregorio 4ac7f80d2f creato globaleass 2019-02-12 17:38:34 +01:00
matteo b24efcc18b commit prepotente 2019-02-12 16:55:46 +01:00
matteo ccde79f4c4 merged 2019-02-11 18:30:29 +01:00
Gregorio d0ce052bc4 finito fvitali 2019-02-11 18:27:21 +01:00
matteo aa575bc9cc Merge branch 'master' of https://bitbucket.org/sco44/tecnologiabanana 2019-02-11 15:38:48 +01:00
matteo 232c33cab8 manca una settimana 2019-02-11 15:38:36 +01:00
Gregorio ca17c2d73b Merge branch 'master' of https://bitbucket.org/sco44/tecnologiabanana 2019-02-11 15:27:40 +01:00
Giampiero Galeazzi ab1b13ec14 Aggiunta tab in VideoInfo per i tweet 2019-01-30 22:47:59 +01:00
Giampiero Galeazzi a981178104 Cambiati i formati delle date di publiccazione del video nei reccomender Random e Related 2019-01-30 22:31:13 +01:00
Gregorio c3b7c22119 Merge branch 'master' of https://bitbucket.org/sco44/tecnologiabanana 2019-01-30 21:51:40 +01:00
Gregorio fd25da5bed modifiche top bar 2019-01-30 21:51:30 +01:00
matteo aa425c1689 Merge branch 'master' of https://bitbucket.org/sco44/tecnologiabanana 2019-01-30 21:45:54 +01:00
matteo 242effb4c8 sono finite le banane 2019-01-30 21:45:29 +01:00
Giampiero Galeazzi f952c66316 Tags are now links 2019-01-30 21:43:15 +01:00
matteo 228e96a114 haribo 2019-01-21 15:55:25 +01:00
matteo 49cdcfeb74 area suggestion 2019-01-20 18:45:00 +01:00
Gregorio 323adb209b aggiornata top bar 2019-01-20 18:31:46 +01:00
Gregorio 3c84183b5f aggiumte scritte alla top bar 2019-01-20 17:08:08 +01:00
matteo 7d94fb34ca succo ace 2019-01-14 19:23:29 +01:00
matteo ab13120448 santo stefano 2018-12-27 19:33:50 +01:00
matteo 36c9c26089 buon natale 2018-12-27 19:26:46 +01:00
matteo c1c69d0797 saturday work 2018-12-16 02:16:01 +01:00
matteo 6f8d7d506a white tabs and other fix 2018-12-15 22:23:43 +01:00
matteo fcb54b629a wikipedia integration 2018-12-15 17:32:51 +01:00
matteo 8f70e3627e removed package-lok.json 2018-12-15 16:58:39 +01:00
matteo e57b90f54c rimosso package-lock.json -- creava problemi con npm 2018-12-15 16:56:23 +01:00
matteo 1b022cc4b3 Merge branch 'wikipedia' 2018-12-15 16:51:21 +01:00
matteo c7b4f0c347 mi sono perso per strada 2018-12-15 16:48:19 +01:00
matteo 9eca8afd9a mo se ne va la madonnadelponde 2018-12-14 16:55:20 +01:00
matteo a7edfd9ee5 Merge branch 'master' of https://bitbucket.org/sco44/tecnologiabanana 2018-12-14 15:49:15 +01:00
matteo a554b26ccb Revert "updated .gitignore"
This reverts commit 3110e36b4f.
2018-12-14 15:48:39 +01:00
Giampiero Galeazzi a0f8689baa -Aggiungo Reccomender Related.
-Modificato App.js in modo che il player sia sulla sinistra e le informazioni sulla destra;inoltre le informazioni del video vengono visualizzate anche per il video iniziale quando si apre la pagina per la prima volta.
-Modificato render di VideoInfo.
2018-12-13 19:47:34 +01:00
matteo 3110e36b4f updated .gitignore 2018-12-13 15:28:53 +01:00
matteo 2b58b105dd visualizza infobox wikipedia e propieta wikidata 2018-12-12 19:16:18 +01:00
matteo b0dbba7673 wikidata wikipedia musicbrainz 2018-12-11 19:48:04 +01:00
matteo ea18c64925 ricerca wikidata con id youtube 2018-12-11 04:01:03 +01:00
matteo 2bca56b881 bobbe malle 2018-12-01 03:04:08 +01:00
59 changed files with 6806 additions and 1546 deletions
+3
View File
@@ -17,9 +17,12 @@
.env.development.local
.env.test.local
.env.production.local
.env
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.idea/
.vs/
db.json
+1 -1
View File
@@ -1 +1 @@
AIzaSyA21Odw9UdfiOJnxvA_eDfOW0OxuUfHKig
'AIzaSyA21Odw9UdfiOJnxvA_eDfOW0OxuUfHKig'
-44
View File
@@ -1,44 +0,0 @@
{
"videos": [
{
"id": "cu3K1njbYqs",
"timesWatched": 1,
"lastWatched": "2018-12-02T20:43:18.202Z"
},
{
"id": "PoSbnAFvqfA",
"timesWatched": 1,
"lastWatched": "2018-12-02T20:43:24.851Z"
},
{
"id": "rzeLynj1GYM",
"timesWatched": 2,
"lastWatched": "2018-12-02T20:43:45.916Z"
},
{
"id": "0J2QdDbelmY",
"timesWatched": 1,
"lastWatched": "2018-12-02T20:49:32.498Z"
},
{
"id": "unRjK82bDLw",
"timesWatched": 2,
"lastWatched": "2018-12-02T20:49:50.769Z"
},
{
"id": "g2N0TkfrQhY",
"timesWatched": 1,
"lastWatched": "2018-12-02T20:52:07.780Z"
},
{
"id": "EM4vblG6BVQ",
"timesWatched": 1,
"lastWatched": "2018-12-02T20:52:43.282Z"
},
{
"id": "tPwqOWK6EFw",
"timesWatched": 1,
"lastWatched": "2018-12-02T20:53:26.049Z"
}
]
}
+87
View File
@@ -0,0 +1,87 @@
process.env['NODE_ENV'] = 'production';
const express = require('express');
const compression = require('compression')
const bodyParser = require('body-parser');
const low = require('lowdb');
const lodashId = require('lodash-id');
const FileAsync = require('lowdb/adapters/FileAsync');
const app = express(); // app is an instance of express
app.use(compression()); // enables gzip compression
app.use(bodyParser.urlencoded({ extended: false })) // parse application/x-www-form-urlencoded
app.use(bodyParser.json()) // parse application/json
// Create database instance and start server
// const adapter = new FileAsync(__dirname + '/db.json');
low(new FileAsync(__dirname + '/db.json')) // production
.then(db => {
db._.mixin(lodashId);
// DATABASE ROUTES
// ==============
app.post('/test', (req, res) => {
// "reason": {
// "fitali": req.body.reason.toString() === "fvitali" ? 1:0,
// "": req.body.reason.toString() === "" ? 1:0,
// "": req.body.reason.toString() === "" ? 1:0,
// }
// try {
// let test = table.updateById(req.body.id, { })
// }
// catch(e) {
// }
res.send(req.body);
});
// ==============
// GET /globpop
app.get('/globpop', (req, res) => {
req.query.id ? res.send(req.query.id) : res.status(400).send('Bad Request');
});
// OPTIONS /videotrack
app.options('/videotrack', (req, res) => {
res.set({
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'POST, GET, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type',
'Access-Control-Max-Age': 600
})
res.status(200).end();
})
// GET /videotrack
app.get('/videotrack', (req, res) => {
const videos = db.get('videos');
req.query.id ? res.send(videos.getById(req.query.id).value()) : res.send(videos.value())
});
// POST /videotrack
app.post('/videotrack', (req, res) => {
const videos = db.get('videos');
const row = videos.getById(req.body.id);
if (row.value()) {
row
.update('timesWatched', (n) => ++n)
.update('lastWatched', () => new Date().toISOString())
.write()
.then(output => res.send(output))
} else {
let newRow = {
"id": req.body.id.toString(),
"timesWatched": 1,
"lastWatched": new Date().toISOString(),
}
videos.push(newRow).last().write().then(output => res.send(output))
}
});
// Set db default values
return db.defaults({ videos: [] }).write();
})
.then(() => {
app.listen(8000, () => console.log('listen on 8000')); // bind to port 8000 as required from specs
});
+151 -24
View File
@@ -1,30 +1,39 @@
process.env['NODE_ENV'] = 'production';
//'AIzaSyA21Odw9UdfiOJnxvA_eDfOW0OxuUfHKig' = 'AIzaSyA21Odw9UdfiOJnxvA_eDfOW0OxuUfHKig';
const express = require('express');
const compression = require('compression')
const bodyParser = require('body-parser');
const low = require('lowdb');
const lodashId = require('lodash-id');
const FileAsync = require('lowdb/adapters/FileAsync');
const app = express(); // app is an instance of express
app.use(compression()); // enables gzip compression
app.use(bodyParser.urlencoded({ extended: false })) // parse application/x-www-form-urlencoded
app.use(bodyParser.json()) // parse application/json
app.use(express.static(__dirname + '/build')); // serve static build site
app.get('/a', (req, res) => {
res.send(process.env);
});
// Create database instance and start server
// const adapter = new FileAsync(__dirname + '/db.json');
low(new FileAsync(__dirname + '/db.json')) // production
// low(new FileAsync('/tmp/db.json')) // dev
.then(db => {
db._.mixin(lodashId);
// DATABASE ROUTES
// ==============
// ==============
app.post('/test',(req,res)=>{
// "reason": {
// "fitali": req.body.reason.toString() === "fvitali" ? 1:0,
// "": req.body.reason.toString() === "" ? 1:0,
// "": req.body.reason.toString() === "" ? 1:0,
// }
app.post('/test', (req, res) => {
// "reason": {
// "fitali": req.body.reason.toString() === "fvitali" ? 1:0,
// "": req.body.reason.toString() === "" ? 1:0,
// "": req.body.reason.toString() === "" ? 1:0,
// }
// try {
// let test = table.updateById(req.body.id, { })
// }
@@ -33,37 +42,155 @@ low(new FileAsync(__dirname + '/db.json')) // production
res.send(req.body);
});
// ==============
// ==============
// OPTIONS /globpop
app.options('/globpop', (req, res) => {
res.set({
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': ' GET, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type',
'Access-Control-Max-Age': 600
})
res.status(200).end();
})
// GET /videotrack/:id?
app.get('/videotrack/:id?', (req, res) => {
const videos = db.get('videos');
req.params.id ? res.send(videos.getById(req.params.id).value()) : res.send(videos.value())
// GET /globpop
app.get('/globpop', (req, res) => {
res.set({
'Access-Control-Allow-Origin': '*'
});
const videos1 = db.get('videos');
let videos = JSON.parse(JSON.stringify(videos1.values()));
if (req.query.id) {
let tmpRes;
let row = videos1.getById(req.query.id);
if (row.value()) {
let tmpA = videos.filter(x => x.id !== req.query.id).map(x => {
return {
"videoID": x.id,
"timesWatched": x.timesWatched,
"prevalentReason": x.reason.sort((a, b) => a.timesWatched - b.timesWatched).reverse()[0].reason,
"lastSelected": x.lastWatched
}
})
tmpRes = {
"site": "site1854.tw.cs.unibo.it",
"recommender": req.query.id.toString(),
"lastWatched": row.value().lastWatched,
"recomended": tmpA
}
}
else {
let tmpA = videos.map(x => {
return {
"videoID": x.id,
"timesWatched": x.timesWatched,
"prevalentReason": x.reason.sort((a, b) => a.timesWatched - b.timesWatched).reverse()[0].reason,
"lastSelected": x.lastWatched
}
})
tmpRes = {
"site": "site1854.tw.cs.unibo.it",
"recommender": req.query.id.toString(),
"lastWatched": "Never Watched",
"recomended": tmpA
}
}
res.send(tmpRes)
} else
res.status(400).send('Bad Request');
});
// OPTIONS /videotrack
app.options('/videotrack', (req, res) => {
res.set({
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'POST, GET, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type',
'Access-Control-Max-Age': 600
})
res.status(200).end();
})
// GET /videotrack
app.get('/videotrack', (req, res) => {
res.set({
'Access-Control-Allow-Origin': '*'
});
const videos1 = db.get('videos').values();
let videos = JSON.parse(JSON.stringify(videos1));
console.log(req.query.prevId)
if (req.query.prevId) {
console.log(videos.length)
let tmp = [];
for (let i = 0; i < videos.length; i++) {
console.log(videos[i]);
for (let j = 0; j < videos[i].reason.length; j++) {
console.log(videos[i].reason[j])
if (videos[i].reason[j].prevId === req.query.prevId) {
let tmp2 = {
...videos[i].reason[j],
'id': videos[i].id
}
tmp.push(tmp2);
}
}
}
res.send(tmp)
} else {
res.send(videos)
}
});
// POST /videotrack
app.post('/videotrack', (req, res) => {
const videos = db.get('videos');
const row = videos.getById(req.body.id);
if(row.value()) {
res.set({
'Access-Control-Allow-Origin': '*'
});
let setReason = function (reason = 'Starter', oldArr = [], prevId) {
let j = oldArr.findIndex(el => el.prevId === prevId && el.reason === reason)
if (j >= 0) {
++oldArr[j].timesWatched;
return oldArr
}
else {
let tmp = {
prevId,
reason,
timesWatched: 1
}
oldArr.push(tmp)
return oldArr
}
}
const videos = db.get('videos');
const row = videos.getById(req.body.id);
if (row.value()) {
row
.update('timesWatched',(n) => ++n)
.update('lastWatched',() => new Date().toISOString())
.write()
.then(output => res.send(output))
.update('timesWatched', (n) => ++n)
.update('lastWatched', () => new Date().toISOString())
.update('reason', (n) => setReason(req.body.reason, n, req.body.prevId))
.write()
.then(output => res.send(200, output))
} else {
let newRow = {
"id": req.body.id.toString(),
"id": req.body.id.toString(),
"timesWatched": 1,
"lastWatched": new Date().toISOString(),
"reason": setReason(req.body.reason, [], req.body.prevId)
}
videos.push(newRow).last().write().then(output => res.send(output))
videos.push(newRow).last().write().then(output => res.send(200, output))
}
});
// Set db default values
return db.defaults({ videos: [] }).write();
})
.then(() => {
app.use(express.static(__dirname + '/build')); // serve static build site
app.get('*', (req, res) => {
res.sendFile(__dirname + '/build/index.html');
});
app.listen(8000, () => console.log('listen on 8000')); // bind to port 8000 as required from specs
});
+4034 -759
View File
File diff suppressed because it is too large Load Diff
+10 -3
View File
@@ -1,16 +1,21 @@
{
"name": "hazetv-alfatube-1",
"version": "0.1.0",
"version": "0.1.1",
"private": true,
"dependencies": {
"@c0b41/ytclear": "^1.0.0",
"axios": "latest",
"body-parser": "^1.18.3",
"bootstrap": "^4.1.3",
"compression": "^1.7.3",
"dotenv": "^6.2.0",
"express": "^4.16.4",
"get-artist-title": "^1.1.1",
"lodash-id": "^0.14.0",
"lowdb": "^1.0.0",
"moment": "^2.23.0",
"moment-duration-format": "^2.2.2",
"node-sass": "^4.11.0",
"react": "^16.6.1",
"react-app-polyfill": "^0.1.3",
"react-bootstrap": "^1.0.0-beta.2",
@@ -18,14 +23,16 @@
"react-height": "^3.0.0",
"react-linkify": "^0.2.2",
"react-router-dom": "^4.3.1",
"react-scripts": "2.1.1",
"react-scripts": "^2.1.3",
"react-youtube": "^7.8.0",
"wikidata-sdk": "^5.15.9",
"wikijs": "^4.8.1",
"youtube-page-token": "^1.0.1"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"build2": "GENERATE_SOURCEMAP=false react-scripts build",
"test": "react-scripts test",
"node": "npm run build && node ./node-entry.js",
"eject": "react-scripts eject"
@@ -36,7 +43,7 @@
"browserslist": [
">0.2%",
"not dead",
"not ie <= 11",
"not ie <= 8",
"not op_mini all"
]
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

+1 -1
View File
@@ -19,7 +19,7 @@
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>react-4</title>
<title>HazeTv</title>
</head>
<body>
<noscript>
+45
View File
@@ -0,0 +1,45 @@
import React, { Component } from 'react'
import { Container, Row, Col, Image } from 'react-bootstrap';
import './css/AboutUs.css';
export default class AboutUs extends Component {
render() {
return (
<Container>
<Row className="show-Container text-center">
<Col xs={12} sm={4} className="person-wrapper">
<Image src="assets/person-1.jpg" roundedCircle className="profile-pic" />
<h3>Matteo Scorzafava</h3>
Wikipedia <br/>
Gestione 15 secondi <br/>
Recommender Search <br/>
Back-end per popolarità <br/>
Recommender similarityArtist <br/>
<p></p>
</Col>
<Col xs={12} sm={4} className="person-wrapper">
<Image src="assets/person-2.jpg" roundedCircle className="profile-pic" />
<h3>Simone Ferrari</h3>
<p>Recommender Random<br/>
Recommender Related<br/>
Recommender Similarity Genere<br/>
Box delle informazioni relative al video
</p>
</Col>
<Col xs={12} sm={4} className="person-wrapper">
<Image src="assets/person-3.jpg" roundedCircle className="profile-pic" />
<h3>Gregorio Giacchetti</h3>
<p>Recommender Recent<br/>
Recommender fvitali<br/>
Gestione della barra dei recommender<br/>
AboutUs <br/>
Recommender similarityArtist
</p>
</Col>
</Row>
</Container>
)
}
}
+78 -43
View File
@@ -1,73 +1,108 @@
import React from 'react';
import { Route, Switch } from 'react-router-dom';
import 'bootstrap/dist/css/bootstrap.css';
import { Row, Col, Button, Collapse } from 'react-bootstrap';
import TopBar from './TopBar';
import VideoPlayer from './VideoPlayer';
import { Route, Switch, withRouter, matchPath } from 'react-router-dom';
import { Row, Col, Button, Collapse } from 'react-bootstrap';
import TopBar from './TopBar';
import VideoPlayer from './VideoPlayer';
import VideoInfo from './VideoInfo';
import VideoList from './VideoList';
import Suggestion from './areainfo/Suggestion';
import VideoList from './VideoList';
import Suggestion from './Suggestion';
import AboutUs from './AboutUs';
import { ReactHeight } from 'react-height';
import './css/App.css';
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
playerHeight: null,
navHeight: null,
blackbgHeight: null,
isInfoToggled: false
};
}
render() {
class App extends React.Component {
state = {
playerHeight: null,
navHeight: null,
blackbgHeight: null,
isInfoToggled: false,
videoId: localStorage['lastId']
};
// componentDidMount() {
// }
// componentDidUpdate(prevProps, prevState) {
// }
static getDerivedStateFromProps(nextProps, prevState) {
let idMatch = matchPath(nextProps.location.pathname, {
path: "/video/:id/:rec?",
exact: true,
strict: false
});
if (idMatch && idMatch.params.id !== prevState.videoId) {
localStorage.setItem('prevId', prevState.videoId)
localStorage.setItem('lastId', idMatch.params.id)
return { videoId: idMatch.params.id };
}
else {
return null;
}
}
render() {
return (
<React.Fragment>
<ReactHeight className="row" onHeightReady={height => { this.setState({ navHeight: height }); console.log('l', height) }}>
<Row id="top">
<TopBar />
</ReactHeight>
<ReactHeight onHeightReady={height => this.setState({ blackbgHeight: height })} className="row upperSection p-2" >
</Row>
<Row className="upperSection">
<Col
xs="12"
lg={this.state.isInfoToggled ? { span: 6, offset: 1, order: 2 } : { span: 7, offset: 1, order: 2 }}
className="align-content-center pt-2 p-1 pr-2">
<ReactHeight onHeightReady={height => this.setState({ playerHeight: height })}>
<Route path={["/video/:id", "/*"]} component={VideoPlayer} /> {/* change component to children */}
</ReactHeight>
lg={this.state.isInfoToggled ? { span: 8, offset: 0, order: 2 } : { span: 9, offset: 0, order: 2 }}
className="blackBg align-content-center">
<Col xs="12" lg={this.state.isInfoToggled ? { span: 10, offset: 1 } : { span: 10, offset: 1 }}>
<ReactHeight onHeightReady={height => { this.setState({ playerHeight: height }) }}>
<VideoPlayer videoId={this.state.videoId} />
</ReactHeight>
</Col>
</Col>
<Col
xs="12"
lg={this.state.isInfoToggled ? { span: 5, order: 1 } : { span: 3, order: 1 }}
className="p-0 p-sm-0"
style={{ 'overflow': "auto", 'max-height': this.state.playerHeight }} >
<div class="d-flex justify-content-center">
<Button
variant="outline-light"
onClick={() => this.setState({ isInfoToggled: !this.state.isInfoToggled })}
className="w-50 pt-1 align-content-center"
aria-controls="infovideo-collapse"
aria-expanded={this.state.isInfoToggled}>
{this.state.isInfoToggled ? "Nascondi informazioni" : "Mostra informazioni"}
</Button>
lg={this.state.isInfoToggled ? { span: 4, order: 1 } : { span: 3, order: 1 }}
className={this.state.isInfoToggled ? "" : "blackBg"}
style={window.innerWidth < 992 ?
{ 'overflow': "auto", 'display': 'fixed', 'maxHeight': this.state.playerHeight * 1.4 + 'px' } :
{ 'overflow': "auto", 'maxHeight': `${this.state.playerHeight}px` }}>
{/* true = style for xs ;; false = style for lg */}
<div className="d-flex justify-content-center">
<Button
variant="outline-light"
onClick={() => this.setState({ isInfoToggled: !this.state.isInfoToggled, dirtyBg: true })}
className="pt-1 "
aria-controls="infovideo-collapse"
aria-expanded={this.state.isInfoToggled}>
{this.state.isInfoToggled ? "Nascondi informazioni" : "Mostra informazioni"}
</Button>
</div>
<Collapse in={this.state.isInfoToggled}>
<div className="pt-1" id="infovideo-collapse">
<Route path={["/video/:id", "/search/:query", "/"]} component={VideoInfo} />
<Route render={(props) => <VideoInfo {...props} key={this.state.videoId} videoId={this.state.videoId} />} />
</div>
</Collapse>
</Col>
</ReactHeight>
<Row style={{ "max-height": `calc(99vh - ${this.state.blackbgHeight}px - ${this.state.navHeight}px)` }} className='mt-1 downSection'>
</Row>
<Row style={{ "maxHeight": `calc(98vh - ${this.state.playerHeight}px - 40px)` }} className='mt-2 downSection'>
<Switch>
<Route exact path='/' component={VideoList} />
<Route path={["/video/:id", "/search/:query"]} component={Suggestion} />
<Route path={"/video/:id"} component={Suggestion} />
<Route path={"/search/:query"} component={Suggestion} />
<Route render={() => <div>URL not found</div>} />
</Switch>
</Row>
<Row id="AboutUs">
<Route exact path="/" component={AboutUs} />
</Row>
</React.Fragment>
);
}
}
export default App;
export default withRouter(App);
+59
View File
@@ -0,0 +1,59 @@
import React, { Component } from 'react';
import { Switch, Route } from 'react-router';
import Random from './reccomenders/Random';
import Related from './reccomenders/Related';
import Search from './reccomenders/Search';
import fvitali from './reccomenders/fvitali';
import popGlobaleAssoluta from './reccomenders/popGlobaleAssoluta';
import Recent from './reccomenders/Recent';
import similarityArtist from './reccomenders/similarityArtist';
import popLocaleAss from './reccomenders/popLocaleAss';
import PopRelLoc from './reccomenders/popRelLoc';
import genreSimilarity from './reccomenders/genreSimilarity';
// and so on..
class Suggestion extends Component {
// static getDerivedStateFromProps(nextProps, prevState) {
// }
// componentDidMount() {
// }
// shouldComponentUpdate(nextProps, nextState) {
// }
// componentDidUpdate(prevProps, prevState) {
// }
// componentWillUnmount() {
// }
render() {
return (
<React.Fragment>
<Switch>
<Route path='/video/:id/genreSimilarity' component={genreSimilarity} />
<Route path='/video/:id/vitali' component={fvitali} />
<Route path='/search/:query' component={Search} />
<Route path='/video/:id/random' component={Random} />
<Route path='/video/:id/popGlobalAssoluta' component={popGlobaleAssoluta} />
<Route path='/video/:id/popLocaleAss' component={popLocaleAss} />
<Route path='/video/:id/recent' component={Recent} />
<Route path='/video/:id/similarityArtist' component={similarityArtist} />
<Route path='/video/:id/popRelLoc' component={PopRelLoc} />
{/* lasciare questo per ultimo */}
<Route component={Related} />
</Switch>
</React.Fragment>
);
}
}
export default Suggestion;
+43 -33
View File
@@ -3,60 +3,70 @@ import {
Navbar,
Nav,
NavDropdown,
//Form,
FormControl,
Button
} from 'react-bootstrap'; //eslint-disable-next-line
import { Link, withRouter } from 'react-router-dom';
import './css/TopBar.css';
// import PropTypes from "prop-types";
class TopBar extends Component {
constructor(props) {
super(props);
this.state = {
query: ''
};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(event) {
state = {
query: ''
};
handleChange = event => {
this.setState({
query: event.target.value
});
}
handleSubmit(event) {
//alert(this.state.query);
handleSubmit = event => {
event.preventDefault();
event.target.reset();
this.props.history.push('/search/'+this.state.query);
// event.target.reset();
this.props.history.push('/search/' + this.state.query);
}
render() {
return (
<React.Fragment>
<Navbar bg="dark" className="py-0 px-1 w-100" expand="md">
<Navbar bg="primary" varian="dark" className="py-0 px-1 w-100" expand="md">
<Navbar.Brand>
<Link to="/">HazeTV</Link>
<Link className="text-light" to="/">HazeTV</Link>
</Navbar.Brand>
<Navbar.Toggle aria-controls="basic-navbar-nav" />
<Navbar.Collapse id="basic-navbar-nav">
<Nav className="mr-auto">
<Link to="/" className="text-light nav-link">Home</Link>
<Link to="/link" className="text-light nav-link">link</Link>
{/* <NavDropdown title="arnaldo" id="basic-nav-dropdown" >
<NavDropdown.Item href="#action/3.1">Action</NavDropdown.Item>
<NavDropdown.Item href="#action/3.2">
Another action
</NavDropdown.Item>
<NavDropdown.Item href="#action/3.3">
Something
</NavDropdown.Item>
<NavDropdown.Divider />
<NavDropdown.Item href="#action/3.4">
Separated link
</NavDropdown.Item>
</NavDropdown> */}
<Link to={{ pathname: `/video/${localStorage['lastId']}/vitali` }} className="text-light nav-link">Vitali</Link>
<Link to={{ pathname: `/video/${localStorage['lastId']}/recent` }} className="text-light nav-link">Recent</Link>
<Link to={{ pathname: `/video/${localStorage['lastId']}/random` }} className="text-light nav-link">Random</Link>
<Link to={{ pathname: `/video/${localStorage['lastId']}/related` }} className="text-light nav-link">Related</Link>
{<NavDropdown title="Similarity" id="basic-nav-dropdown" >
<Link to={{ pathname: `/video/${localStorage['lastId']}/similarityArtist` }} className="text-light nav-link">
Artist
</Link>
<Link to={{ pathname: `/video/${localStorage['lastId']}/genreSimilarity` }} className="text-light nav-link">
Genre
</Link>
</NavDropdown>}
{<NavDropdown title="Popolarità locale" id="basic-nav-dropdown" >
<Link to={{ pathname: `/video/${localStorage['lastId']}/popLocaleAss` }} className="text-light nav-link">
Assoluta
</Link>
<Link to={{ pathname: `/video/${localStorage['lastId']}/popRelLoc` }} className="text-light nav-link">
Relativa
</Link>
</NavDropdown>}
<Link to={{ pathname: `/video/${localStorage['lastId']}/popGlobalAssoluta` }} className="text-light nav-link">popGlobalAssoluta</Link>
</Nav>
<form className="form-inline" onSubmit={this.handleSubmit}>
<FormControl
@@ -65,7 +75,7 @@ class TopBar extends Component {
className="mr-sm-2"
onChange={this.handleChange}
/>
<Button type="submit" variant="outline-success">Search</Button>
<Button type="submit" variant="secondary">Search</Button>
</form>
</Navbar.Collapse>
</Navbar>
+107 -219
View File
@@ -1,256 +1,144 @@
import React, { Component } from 'react';
import Wikipedia from './areainfo/Wikipedia';
import { Col, Tabs, Tab,} from 'react-bootstrap';
import React from 'react';
import Wikipedia from './Wikipedia';
import { Card, Tabs, Tab } from 'react-bootstrap';
import { Link } from 'react-router-dom';
import axios from 'axios';
import Linkify from "react-linkify";
import Card from "react-bootstrap/lib/Card";
import moment from 'moment';
import momentDurationFormat from 'moment-duration-format';
import momentDurationFormat from 'moment-duration-format'; //eslint-disable-line no-unused-vars
import './css/VideoInfo.scss';
const ytclear = require('@c0b41/ytclear');
class VideoInfo extends Component {
constructor(props) {
super(props);
this.state = {
error: null,
isLoaded: false,
VideoDetails: {},
videoId: '0J2QdDbelmY',
comments: []
};
this.momentDuration = this.momentDuration.bind(this);
class VideoInfo extends React.Component {
state = {
error: null,
isLoaded: false,
VideoDetails: {},
videoId: '',
comments: [],
others: {}
};
momentDuration(duration) {
return moment.duration(duration).format('hh:mm:ss')
}
momentDuration(duration){
return moment.duration(duration).format('hh:mm:ss');
ISO_8601parse(a) {
return moment(a, moment.ISO_8601).format('ddd, DD/MM/YYYY hh:mm:ss');
}
// componentWillMount() {
// if (this.props.match.params.id) {
// if (this.props.match.params.id === this.state.videoId) { }
// else {
// this.setState({ videoId: this.props.match.params.id });
// }
// }
// }
componentDidMount() {
console.log('didmount',this.state.videoId)
axios.all([
return axios.all([
axios.get('https://www.googleapis.com/youtube/v3/videos', { //Richiesta per tutte le info sul video
params: {
'part': 'snippet,contentDetails,statistics,topicDetails',
'id': this.state.videoId,
'key': 'AIzaSyA21Odw9UdfiOJnxvA_eDfOW0OxuUfHKig'
}
}), axios.get('https://www.googleapis.com/youtube/v3/commentThreads', { //Richiesta per ottenere i commenti del video
params: {
'part': 'snippet',
'videoId': this.state.videoId,
'order': 'relevance',
'key': 'AIzaSyA21Odw9UdfiOJnxvA_eDfOW0OxuUfHKig'
}
})])
params: {
'part': 'snippet,contentDetails,statistics,topicDetails',
'id': this.props.videoId,
'key': 'AIzaSyA21Odw9UdfiOJnxvA_eDfOW0OxuUfHKig' // 'AIzaSyA21Odw9UdfiOJnxvA_eDfOW0OxuUfHKig' in production
}
}), axios.get('https://www.googleapis.com/youtube/v3/commentThreads', { //Richiesta per ottenere i commenti del video
params: {
'part': 'snippet',
'videoId': this.props.videoId,
'order': 'relevance',
'key': 'AIzaSyA21Odw9UdfiOJnxvA_eDfOW0OxuUfHKig' // 'AIzaSyA21Odw9UdfiOJnxvA_eDfOW0OxuUfHKig' in production
}
})])
.then(axios.spread((videosResponse, commentThreadResponse) => {
this.setState({
VideoDetails: videosResponse.data.items,
VideoDetails: videosResponse.data.items[0],
comments: commentThreadResponse.data.items,
isLoaded: true
});
let index = videosResponse.data.items[0].topicDetails.relevantTopicIds.indexOf('/m/04rlf');
videosResponse.data.items[0].topicDetails.relevantTopicIds.splice(index, 1);
localStorage.setItem('lastTopic', videosResponse.data.items[0].topicDetails.relevantTopicIds);
}), error => {
console.error(error);
this.setState({
isLoaded: true,
error
error,
isLoaded: true
});
});
}
componentDidUpdate(prevProps, prevState, snapshot) {
console.info('didup', this.props.match.params.id)
// this.setState((state, props) => { return { }});
if (this.props.match.params.id && !(prevProps.match.params.id === this.props.match.params.id)) {
this.setState({ videoId: this.props.match.params.id, isLoaded: false });
axios.all([
axios.get('https://www.googleapis.com/youtube/v3/videos', { //Richiesta per tutte le info sul video
params: {
'part': 'snippet,contentDetails,statistics,topicDetails',
'id': this.props.match.params.id,
'key': 'AIzaSyA21Odw9UdfiOJnxvA_eDfOW0OxuUfHKig'
}
}), axios.get('https://www.googleapis.com/youtube/v3/commentThreads', { //Richiesta per ottenere i commenti del video
params: {
'part': 'snippet',
'videoId': this.props.match.params.id,
'order': 'relevance',
'key': 'AIzaSyA21Odw9UdfiOJnxvA_eDfOW0OxuUfHKig'
}
})])
.then(axios.spread((videosResponse, commentThreadResponse) => {
this.setState({
VideoDetails: videosResponse.data.items,
comments: commentThreadResponse.data.items,
isLoaded: true
});
}), error => {
console.error(error);
this.setState({
isLoaded: true,
error
});
});
}
}
// shouldComponentUpdate(nextProps, nextState){
// console.log('shouldup', nextProps, nextState)
// if(nextProps.match.params.id)
// return console.log('a',!(nextProps.match.params.id === this.state.videoId)) && !(nextProps.match.params.id === this.state.videoId) ;
// else if(!(nextState.videoId === this.state.videoId))
// return console.log('b') && true;
// else if(nextState.isLoaded)
// return console.log('d') && true;
// else
// return console.log('c') && false;
// }
// componentWillReceiveProps(nextProps){
// if (nextProps.match.params.id === this.state.videoId) { console.log('1',nextProps)}
// else if (nextProps.match.params.id) {
// console.log('2',nextProps)
// this.setState({ videoId: nextProps.match.params.id });
// axios.all([
// axios.get('https://www.googleapis.com/youtube/v3/videos', { //Richiesta per tutte le info sul video
// params: {
// 'part': 'snippet,contentDetails,statistics,topicDetails',
// 'id': nextProps.match.params.id,
// 'key': 'AIzaSyA21Odw9UdfiOJnxvA_eDfOW0OxuUfHKig'
// }
// }), axios.get('https://www.googleapis.com/youtube/v3/commentThreads', { //Richiesta per ottenere i commenti del video
// params: {
// 'part': 'snippet',
// 'videoId': nextProps.match.params.id,
// 'order': 'relevance',
// 'key': 'AIzaSyA21Odw9UdfiOJnxvA_eDfOW0OxuUfHKig'
// }
// })])
// .then(axios.spread((videosResponse, commentThreadResponse) => {
// this.setState({
// VideoDetails: videosResponse.data.items,
// comments: commentThreadResponse.data.items,
// isLoaded: true
// });
// }), error => {
// console.error(error);
// this.setState({
// isLoaded: true,
// error
// });
// });
// }
// else{
// console.log('3',nextProps)
// }
// }
render() {
const { error, isLoaded, VideoDetails, comments } = this.state;
const momentDuration = this.momentDuration; // bind momentDuration in render to the actual method
const momentDuration = this.momentDuration;
const ISO_8601parse = this.ISO_8601parse;
if (error) {
let htmlRegEx = /<\/?\w+((\s+\w+(\s*=\s*(?:".*?"|'.*?'|[\^'">\s]+))?)+\s*|\s*)\/?>/gm;
return (
<React.Fragment>
Error: {error.message} -- Cannot get {error.config.url}
</React.Fragment>
<span className="text-white">
<span>{}</span>
Error: {error.message} -- {error.response.data && error.response.data.error.message.replace(htmlRegEx, '')}
</span>
);
} else if (!isLoaded) {
return <React.Fragment>Loading...</React.Fragment>;
return <div className="text-center">
<div className="lds-ellipsis"><div></div><div></div><div></div><div></div></div>
</div>;
} else {
return (
<React.Fragment>
<Col >
{/* codice per tabs reccomenders */}
<Tabs defaultActiveKey={1} id="tabs" animation={0}>
<Tab eventKey={1} title="Video">
<div style={{color : 'white'}}>
{this.state.VideoDetails.map(function(item,index){
return(
<div key={index}>
<h5>{item.snippet.title}</h5>
<p><b>ID Video :</b> {item.id}<br/>
<b>Publish Time :</b> {item.snippet.publishedAt.replace(/(\d+)-(\d+)-(\d+)T(\d+):(\d+):(\d+).000Z/,function(match,p1,p2,p3,p4,p5,p6){
return(p3+'/'+p2+'/'+p1+' - '+p4+':'+p5+':'+p6);
})}<br/>
<b>Channel Name :</b> {item.snippet.channelTitle}<br/>
<b>Description :</b><br/><Linkify>{item.snippet.description}</Linkify><br/>
<b>Tags :</b><br/>{item.snippet.tags.map(function(element,index){
if(index===4)
return (<br/>);
return(
<span key={index} style={{margin : '0.5rem',textDecoration : 'underline'}}>{element}</span>
)
})}
</p>
</div>
)
}
)}
</div>
</Tab>
<Tab eventKey={2} title="Tecnical Informations">
<div style={{color:'white'}}>
{this.state.VideoDetails.map(function (item,index) {
return(
<div key={index}>
<h5 style={{marginTop : '1rem'}}>{item.snippet.title}</h5>
<p><b>Duration :</b>{ momentDuration(item.contentDetails.duration) }<br/>
<b>Definition :</b> {item.contentDetails.definition}<br/>
<b>Dimension :</b> {item.contentDetails.dimension}<br/>
<b> Views :</b> {item.statistics.viewCount}<br/>
<b>Likes :</b> {item.statistics.likeCount}<br/>
<b>Dislikes :</b> {item.statistics.dislikeCount}
</p>
</div>
)
})}
</div>
</Tab>
<Tab eventKey={3} title="Comment">
<div>
{this.state.comments.map(function (item,index) {
return(
<div key={index}>
<Card style={{width: '30rem',fontSize : 'small',borderRadius : '1rem'}} className="my-1">
<Card.Body>
<Card.Title>
<Card.Img src={item.snippet.topLevelComment.snippet.authorProfileImageUrl} style={{width : '3rem', height : '3rem'}} />{item.snippet.topLevelComment.snippet.authorDisplayName}
</Card.Title>
<Card.Text>
<Linkify>
{item.snippet.topLevelComment.snippet.textDisplay}
</Linkify>
</Card.Text>
</Card.Body>
</Card>
</div>
)
})}
</div>
</Tab>
<Tab eventKey={4} title="Wikipedia">
<div>
</div>
</Tab>
</Tabs>
</Col>
<span className="h5 text-white">{VideoDetails.snippet.title}</span>
<Tabs defaultActiveKey="video" id="tabs">
<Tab className="text-white" eventKey="video" title="Video">
<p>
<b>ID Video: </b>{VideoDetails.id}<br />
<b>Published: </b>{ISO_8601parse(VideoDetails.snippet.publishedAt)}<br />
<b>Channel name: </b>{VideoDetails.snippet.channelTitle}<br />
<b>Description: </b><Linkify>{VideoDetails.snippet.description}</Linkify><br />
<b>Tags: </b>{VideoDetails.snippet.tags ?
VideoDetails.snippet.tags.map((tag, i) =>
(<span key={i}><Link to={{ pathname: '/search/' + tag }}>{tag}</Link>{" "}</span>))
: ""}
</p>
</Tab>
<Tab className="text-white" eventKey="techinfo" title="Tecnical Informations">
<p>
<b>Duration: </b>{momentDuration(VideoDetails.contentDetails.duration)}<br />
<b>Definition: </b> {VideoDetails.contentDetails.definition}<br />
<b>Dimension: </b> {VideoDetails.contentDetails.dimension}<br />
<b>Views: </b> {VideoDetails.statistics.viewCount}<br />
<b>Likes: </b> {VideoDetails.statistics.likeCount}{String.fromCodePoint(0x1F44D)}<br />
<b>Dislikes: </b> {VideoDetails.statistics.dislikeCount}{String.fromCodePoint(0x1F44E)}
</p>
</Tab>
<Tab eventKey="comments" title="Comment">
{comments.map(function (comment, index) {
return (
<Card key={index} className="my-1">
<Card.Body>
<Card.Title>
<Card.Img src={comment.snippet.topLevelComment.snippet.authorProfileImageUrl} style={{ width: '3rem', height: '3rem' }} />
{comment.snippet.topLevelComment.snippet.authorDisplayName}
</Card.Title>
<Card.Text>
<Linkify>
{comment.snippet.topLevelComment.snippet.textOriginal}
</Linkify>
</Card.Text>
</Card.Body>
</Card>
)
})}
</Tab>
<Tab className="text-white" eventKey="wikipedia" title="Wikipedia">
<Wikipedia
title={'undefined' !== typeof this.props.location.state ? this.props.location.state.title : undefined}
title2={ytclear(this.state.VideoDetails.snippet.title).split('-')[1]}
artist={'undefined' !== typeof this.props.location.state ? this.props.location.state.artist : undefined}
artist2={ytclear(this.state.VideoDetails.snippet.title).split('-')[0]}
key={this.props.videoId}
videoId={this.props.videoId}
/>
</Tab>
</Tabs>
</React.Fragment>
);
}
}
}
VideoInfo.propTypes = {
};
export default VideoInfo;
export default VideoInfo;
+90 -65
View File
@@ -1,53 +1,55 @@
import React from 'react';
import { Col, ListGroup, Card, CardDeck } from 'react-bootstrap'; // eslint-disable-line no-unused-vars
import { Col, ListGroup, Card, CardDeck, Media } from 'react-bootstrap'; // eslint-disable-line no-unused-vars
import axios from 'axios'; // eslint-disable-line no-unused-vars
import { Link } from 'react-router-dom'; // eslint-disable-line no-unused-vars
import './css/VideoList.css';
/*eslint no-console: ["error", { allow: ["warn", "error", "info"] }] */
class VideoList extends React.Component {
constructor(props) {
super(props);
this.state = {
error: null,
isLoaded: false,
items: [],
headers: null
};
this.getThumbnails = this.getThumbnails.bind(this);
}
state = {
error: null,
isLoaded: false,
items: [],
headers: null
};
componentDidMount() {
axios.get('http://site1825.tw.cs.unibo.it/video.json').then(
response => {
this.shuffleArray(response.data);
this.getThumbnails(response.data);
},
// Note: it's important to handle errors here
// instead of a catch() block so that we don't swallow
// exceptions from actual bugs in components.
error => {
console.error(error);
this.setState({
isLoaded: true,
error
});
}
);
localStorage['fixedVideoList'] ?
this.setState({
items: this.shuffleArray(JSON.parse(localStorage.getItem('fixedVideoList'))),
isLoaded: true
}) :
axios.get('http://site1825.tw.cs.unibo.it/video.json').then(
response => {
this.shuffleArray(response.data);
this.getThumbnails(response.data)
.then(videoList => localStorage.setItem('fixedVideoList', JSON.stringify(videoList)));
},
// Note: it's important to handle errors here
// instead of a catch() block so that we don't swallow
// exceptions from actual bugs in components.
error => {
console.error(error);
this.setState({
isLoaded: true,
error
});
}
)
}
// METODI
getThumbnails(videoItems) {
let idToLookFor = videoItems.map(item => item.videoID);
let finalChunk = (videoItems.length % 50) + 100;
axios
return axios
.all([
// chain 3 parallel get
axios.get('https://www.googleapis.com/youtube/v3/videos', {
params: {
part: 'snippet',
id: idToLookFor.slice(0, 50).toString(),
key: 'AIzaSyA21Odw9UdfiOJnxvA_eDfOW0OxuUfHKig',
key: 'AIzaSyA21Odw9UdfiOJnxvA_eDfOW0OxuUfHKig', // 'AIzaSyA21Odw9UdfiOJnxvA_eDfOW0OxuUfHKig' in production
fields: 'items(id,snippet/thumbnails/medium)'
}
}),
@@ -56,7 +58,7 @@ class VideoList extends React.Component {
params: {
part: 'snippet',
id: idToLookFor.slice(50, 100).toString(),
key: 'AIzaSyA21Odw9UdfiOJnxvA_eDfOW0OxuUfHKig',
key: 'AIzaSyA21Odw9UdfiOJnxvA_eDfOW0OxuUfHKig', // 'AIzaSyA21Odw9UdfiOJnxvA_eDfOW0OxuUfHKig' in production
fields: 'items(id,snippet/thumbnails/medium)'
}
}),
@@ -65,46 +67,47 @@ class VideoList extends React.Component {
params: {
part: 'snippet',
id: idToLookFor.slice(100, finalChunk).toString(),
key: 'AIzaSyA21Odw9UdfiOJnxvA_eDfOW0OxuUfHKig',
key: 'AIzaSyA21Odw9UdfiOJnxvA_eDfOW0OxuUfHKig', // 'AIzaSyA21Odw9UdfiOJnxvA_eDfOW0OxuUfHKig' in production
fields: 'items(id,snippet/thumbnails/medium)'
}
})
])
.then(
axios.spread((res1, res2, res3) => {
let allres = [].concat(
res1.data.items,
res2.data.items,
res3.data.items
);
allres.map(resCurrentValue => {
Object.defineProperty(
videoItems.find(videoItem => {
return videoItem.videoID === resCurrentValue.id;
}),
'thumbnail',
{ value: resCurrentValue.snippet.thumbnails.medium.url }
[...res1.data.items, ...res2.data.items, ...res3.data.items]
.map(resCurrentValue =>
Object.defineProperty(
videoItems.find(videoItem => {
return videoItem.videoID === resCurrentValue.id;
}),
'thumbnail',
{
value: resCurrentValue.snippet.thumbnails.medium.url,
enumerable: true
}
)
);
});
this.setState({
isLoaded: true,
items: videoItems
});
return new Promise((resolve, reject) => resolve(videoItems))
})
);
)
}
shuffleArray(videoarray) {
for (let i = videoarray.length - 1; i > 0; i--) {
shuffleArray(videoArray) {
for (let i = videoArray.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[videoarray[i], videoarray[j]] = [videoarray[j], videoarray[i]]; // eslint-disable-line no-param-reassign
[videoArray[i], videoArray[j]] = [videoArray[j], videoArray[i]]; // eslint-disable-line no-param-reassign
}
return videoArray
// Durstenfeld shuffle.
// https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle#The_modern_algorithm
}
render() {
const { error, isLoaded, items, headers } = this.state;
const { error, isLoaded, items } = this.state;
if (error) {
return (
<React.Fragment>
@@ -112,27 +115,22 @@ class VideoList extends React.Component {
</React.Fragment>
);
} else if (!isLoaded) {
return (
<React.Fragment>
<Col>
<p className="text-center">Loading...</p>
</Col>
</React.Fragment>
);
return <Col className="text-center">
<div className="lds-ellipsis"><div></div><div></div><div></div><div></div></div>
</Col>;
} else {
return (
<React.Fragment>
{items.map(item => (
<Col key={item.videoID} lg={{ span: 3 }} md="6">
<Card className="my-1 carta">
<Link
to={{
pathname: '/video/' + item.videoID,
state: {
artist: item.artist,
title: item.title
}
}}
<Link to={{
pathname: '/video/' + item.videoID,
state: {
artist: item.artist,
title: item.title
}
}}
>
{/* {item.artist} - {item.title} {item.videoID} {item.category} */}
<Card.Img
@@ -152,6 +150,33 @@ class VideoList extends React.Component {
</Col>
))}
</React.Fragment>
// <ul className="list-unstyled">{
// items.map(item => (
// <Link
// key={item.videoId}
// as="li" className="media mx-5 my-1 suggItem"
// to={{
// pathname: '/video/' + item.videoID,
// state: {
// artist: item.artist,
// title: item.title
// }
// }}>
// <img
// width={240}
// height={120}
// className="align-self-center mr-3"
// src={item.thumbnail}
// alt={'Thumbnail of ' + item.title}
// />
// <Media.Body>
// <h5>{item.artist} - {item.title}</h5>
// <p>{item.category}</p>
// </Media.Body>
// </Link>
// ))}</ul>
);
}
}
+97 -71
View File
@@ -1,78 +1,53 @@
import React, { Component } from 'react'; // eslint-disable-line no-unused-vars
import React from 'react'; // eslint-disable-line no-unused-vars
import YouTube from 'react-youtube'; // eslint-disable-line no-unused-vars
import axios from 'axios';
import { withRouter } from 'react-router-dom';
/* eslint no-console: ["error", { allow: ["info", "error", "warn"] }] */
class VideoPlayer extends Component {
constructor(props) {
super(props);
this.state = {
opt: {
height: '390',
width: '640',
playerVars: {
// https://developers.google.com/youtube/player_parameters
autoplay: 0,
modestbranding: 1
}
},
videoId: '0J2QdDbelmY',
promise1: null,
promise2: null,
outsideReject1: null,
outsideReject2: null
};
this._onPlay = this._onPlay.bind(this);
this._onStateChange = this._onStateChange.bind(this);
this._onReady = this._onReady.bind(this);
}
componentWillUpdate(nextProps, nextState) {
if (nextProps.match.params.id === this.props.match.params.id) {
} else if (nextProps.match.params.id) {
this.setState((state, props) => {
return { videoId: props.match.params.id };
});
}
}
componentWillReceiveProps(nextProps) {
// if (nextProps.location !== this.props.location) {}
}
componentWillMount() {
if (this.props.match.params.id)
this.setState((state, props) => {
return { videoId: props.match.params.id };
});
}
componentWillUnmount() {}
class VideoPlayer extends React.Component {
state = {
opt: {
// height: '390',
// width: '640',
playerVars: {
// https://developers.google.com/youtube/player_parameters
autoplay: 0,
modestbranding: 1,
fs: 0,
iv_load_policy: 3,
rel: 0,
origin//: 'http://site1854.tw.cs.unibo.it'
}
},
promise1: null,
promise2: Promise,
outsideReject1: null,
outsideReject2: null
};
render() {
return (
<React.Fragment>
<YouTube
opts={this.state.opt}
videoId={this.state.videoId}
onReady={this._onReady}
onPlay={this._onPlay}
onError={this._onError}
onStateChange={this._onStateChange}
className="embed-responsive-item"
containerClassName="embed-responsive embed-responsive-16by9"
/>
</React.Fragment>
<YouTube
opts={this.state.opt}
videoId={this.props.videoId}
onReady={this._onReady}
onPlay={this._onPlay}
onError={this._onError}
onStateChange={this._onStateChange}
className="embed-responsive-item"
containerClassName="embed-responsive embed-responsive-16by9"
/>
);
}
// access to player in all event handlers via event.target
_onError(event) {
console.error('Error: ', event);
console.error('Player error: ', event);
}
_onPlay(event) {}
_onPlay(event) { }
_onReady(event) {}
_onReady(event) { }
_onStateChange(event) {
let timerId1, timerId2;
@@ -102,8 +77,8 @@ class VideoPlayer extends Component {
promise1: new Promise((resolve, reject) => {
timerId1 = window.setInterval(() => {
if (event.target.getCurrentTime() > 15)
resolve(event.target.j.videoData.video_id);
}, 1000);
resolve(event.target.getVideoData().video_id);
}, 500);
this.setState({ outsideReject1: reject });
})
});
@@ -111,31 +86,82 @@ class VideoPlayer extends Component {
.then(
videoIdWatched => {
// post request to local db
let reason = new URLSearchParams(this.props.location.search).get("ref");
axios
.post('http://localhost:8000/videotrack', {
id: videoIdWatched.toString()
.post('http://site1854.tw.cs.unibo.it/videotrack', {
id: videoIdWatched.toString(),
reason: reason || undefined,
prevId: localStorage['prevId']
})
.then(
response => console.info(response.data),
error => console.info(error)
error => this.setState({ error })//console.error(error, videoIdWatched.toString())
);
// local storage logic for recent reccomender
let tmp = JSON.parse(localStorage.getItem('lastWatched'))
let j = tmp.findIndex(el => el.id.toString() === videoIdWatched.toString())
if (j < 0) {
if (localStorage['prevId'] === '1') {
//per primo video
tmp.push({
'id': videoIdWatched.toString(),
'lastWatched': new Date(),
'timesWatched': 1,
'prevVideos': []
})
}
else {
//Prima volta video generico
tmp.push({
'id': videoIdWatched.toString(),
'lastWatched': new Date(),
'timesWatched': 1,
'prevVideos': [{ id: [localStorage["prevId"]].toString() }]
})
}
}
else {
//altre volte
tmp[j].timesWatched++;
tmp[j].lastWatched = new Date();
let z = tmp[j].prevVideos.findIndex(el => el.id === localStorage["prevId"].toString())
if (z < 0) {
tmp[j].prevVideos.push({
'id': localStorage["prevId"].toString()
})
}
}
tmp.sort((a, b) => new Date(b.lastWatched).getTime() - new Date(a.lastWatched).getTime());
localStorage.setItem('lastWatched', JSON.stringify(tmp));
//.finally(()=>{})
clearInterval(timerId1);
return new Promise ((resolve,reject) => resolve(videoIdWatched))
},
error => console.error(error)
)
.finally(() => this.setState({ promise1: null }));
break;
.finally((id) => this.setState({ promise1: null, outsideReject1: null }));
break;
case 5:
if (this.state.promise1)
if (this.state.promise1)
this.state.outsideReject1('Video changed');
break;
default:
break;
}
});
return new Promise ((resolve,reject) => resolve('ok'))
})
.finally((res) => { this.setState({ promise2: null, outsideReject2: null }) });
}
}
_onStateChange = this._onStateChange.bind(this);
}
export default VideoPlayer;
export default withRouter(VideoPlayer);
+324
View File
@@ -0,0 +1,324 @@
import React, { Component } from 'react';
import { Link } from 'react-router-dom';
import wikijs from 'wikijs';
import wdk from 'wikidata-sdk';
import axios from 'axios';
import { Table, Col } from 'react-bootstrap';
import moment from 'moment';
import "./css/Wikipedia.css"
/*eslint no-console: ["error", { allow: ["warn", "error"] }] */
class Wikipedia extends Component {
state = {
wikidata: null,
wikipedia: null,
musicbrainz: null,
props: {},
error: null,
isWikiLoaded: false,
isWDataLoaded: false,
isMBLoaded: false,
isLoaded: false,
wikidatakeys: null,
wikipediakeys: null
};
ISO_8601toYYYY(a) {
return moment(a, [moment.ISO_8601, 'YYYY']).format('YYYY');
}
getExternalLink(P, value) {
switch (P) {
case "P436": //MB release group id
return (<a target="_blank" rel="noopener noreferrer" href={`https://musicbrainz.org/release-group/${value}`}>{`${value}`}</a>)
case "P435": // MB work id
return (<a target="_blank" rel="noopener noreferrer" href={`https://musicbrainz.org/work/${value}`}>{`${value}`}</a>)
case "P1954": // discogs id
return (<a target="_blank" rel="noopener noreferrer" href={`https://www.discogs.com/master/${value}`}>{`${value}`}</a>)
case "P2624": // metrolyrics id
return (<a target="_blank" rel="noopener noreferrer" href={`http://www.metrolyrics.com/${value}`}>{`${value}`}</a>)
case "P1651": // youtubeid
return (<Link to={{ pathname: `/video/${value}` }}>{`${value}`}</Link>)
case "P2581":
return (<a target="_blank" rel="noopener noreferrer" href={`https://babelnet.org/synset?word=bn:${value}`}>{value}</a>)
case "P1827":
return (<a target="_blank" rel="noopener noreferrer" href={`https://musicbrainz.org/iswc/${value}`}>{value}</a>)
default:
return (`${value}`);
}
}
getMusicBrainzFromWData = (P175, P435=undefined) => { // P175 = artist P435 = workid
const musicbrainzBaseUrl = "https://musicbrainz.org/ws/2"; // more readable
let artist = this.props.artist || this.props.artist2;
let title = this.props.title || this.props.title2;
if (P175.toLowerCase() === artist.trim().toLowerCase()) {
axios
.get(`https://musicbrainz.org/ws/2/recording`, {
params: {
'query': `${title.trim()} AND artistname:${artist.trim()}`, //AND release:${P361}
'limit': 1,
'fmt': 'json'
}
})
.then(resP175EqArtist => {
axios
.get(`${musicbrainzBaseUrl}/recording/${resP175EqArtist.data.recordings[0].id}`, {
params: {
'inc': 'work-rels+artists+releases',
'fmt': 'json'
}
})
.then(resRecordingQuery => {
let musicbrainzWorkId = resRecordingQuery.data.relations.map(x => x['target-type'] === 'work' ? x.work.id.toString() : "") || P435.toString();
axios.get(`${musicbrainzBaseUrl}/work/${musicbrainzWorkId}`, {
params:{
'inc': 'artist-rels url-rels',
'limit': 1,
'offset': 0,
'fmt': 'json'
}
})
.then(musicbrainzWorkRes => {
this.setState({ musicbrainz: musicbrainzWorkRes.data, isMBLoaded: true })
},
error => console.error(error))
}, err => { console.error(err) })
}
, err => console.error(err))
}
else if (P175.toLowerCase() === title.trim().toLowerCase()) { // video title was title - artist
axios
.get(`https://musicbrainz.org/ws/2/recording`, {
params: {
'query': `"${artist.trim()}" AND artistname:"${title.trim()}"`, //AND release:${P361}
'limit': 1,
'fmt': 'json'
}
})
.then(resP175EqTitle => {
axios
.get(`${musicbrainzBaseUrl}/recording/${resP175EqTitle.data.recordings[0].id}`, {
params: {
'inc': 'work-rels+artists+releases',
'fmt': 'json'
}
})
.then(resRecordingQuery => {
let musicbrainzWorkId = resRecordingQuery.data.relations.map(x => x['target-type'] === 'work' ? x.work.id.toString() : "") || P435.toString();
axios.get(`${musicbrainzBaseUrl}/work/${musicbrainzWorkId}`, {
params:{
'inc': 'artist-rels url-rels',
'limit': 1,
'offset': 0,
'fmt': 'json'
}
})
.then(musicbrainzWorkRes => {
this.setState({ musicbrainz: musicbrainzWorkRes.data, isMBLoaded: true })
},
error => console.error(error))
}, err => { console.error(err) })
}
, err => console.error(err))
}
}
getWikidataPage(wikidataPXXXXResID) {
return axios.get(wdk.getEntities(wikidataPXXXXResID, 'en'))
.then(wikidataPage => {
let wikidatatmp = wikidataPage.data.entities[Object.keys(wikidataPage.data.entities)[0]];
this.setState({
wikidata: wikidatatmp
});
axios.all([
axios.get(wdk.getManyEntities(Object.keys(wikidatatmp.claims), 'en'))
, axios.get(wdk.getManyEntities(
Object.keys(wikidatatmp.claims)
.map(x => wikidatatmp.claims[x][0].mainsnak.datavalue.value.id)
.filter(x => typeof x === 'string'), 'en'))
])
.then(
axios.spread((resKey, resQ) => {
this.setState({
wikidatakeys: Object.entries(resKey.data.entities),
wikidataQ: resQ.data.entities,
isWDataLoaded: true
})
try{
this.getMusicBrainzFromWData(
resQ.data.entities[wikidatatmp.claims.P175[0].mainsnak.datavalue.value.id].labels.en.value,
wikidatatmp.claims.P435[0].mainsnak.datavalue.value
);
}
catch(e){
console.error(e)
this.getMusicBrainzFromWData(
resQ.data.entities[wikidatatmp.claims.P175[0].mainsnak.datavalue.value.id].labels.en.value
);
}
}))
if (wikidatatmp.sitelinks.enwiki)
wikijs()
.page(wikidatatmp.sitelinks.enwiki.title)
.then(page => Promise.all([page.fullInfo()]))
.then(wikipediaRes => this.setState({
wikipedia: {
// "desc": wikipediaRes[1],
"info": wikipediaRes[0].general
},
wikipediakeys: Object.keys(wikipediaRes[0].general),
isWikiLoaded: true
}))
else {
this.findWikiPage();
}
}, error => console.error(error))
}
findWikiPage = () => {
let wikiquery = `${'undefined' === typeof this.props.title ? ('undefined' === typeof this.props.title2 ? this.props.artist2 : this.props.title2) : this.props.title.trim()} ${'undefined' === typeof this.props.title && 'undefined' === typeof this.props.title2 ? "" : `(${'undefined' === typeof this.props.artist ? this.props.artist2.trim() : this.props.artist.trim()} song)`}`;
return wikijs()
.find(wikiquery)
.then(res => {
return Promise.all([res.fullInfo()]);
}) // find by props.title (props.artist song)
.then(wikipediaRes => this.setState({
wikipedia: {
"info": wikipediaRes[0].general
},
wikipediakeys: Object.keys(wikipediaRes[0].general),
isWikiLoaded: true
}))
}
componentDidMount() {
let timerId1;
axios.get(wdk.getReverseClaims('P1651', this.props.videoId)) // P1651 is youtube_video_id property
.then(wikidataP1651Res => {
if (wikidataP1651Res.data.results.bindings.length) // got a match
this.getWikidataPage(wdk.simplify.sparqlResults(wikidataP1651Res.data))// set in state wikidata
else { //oof -- no P1651 :(
let title = this.props.title || this.props.title2;
axios.get(wdk.searchEntities(`${title.trim()}`))
.then(wikidataSearchRes => {
try{
this.getWikidataPage(wikidataSearchRes.data.search[0].title)
}
catch(e){
console.error(e)
}
})
}
return new Promise((resolve, reject) => {
timerId1 = window.setInterval(() => {
if (this.state.wikidataQ == undefined || this.state.wikidata == undefined) { }
else{
try {
resolve({
artist: this.state.wikidataQ[this.state.wikidata.claims["P175"][0].mainsnak.datavalue.value.id].labels.en.value.toString(),
genre: this.state.wikidataQ[this.state.wikidata.claims["P136"][0].mainsnak.datavalue.value.id].labels.en.value.toString()
})
}
catch{
resolve({ artist: null, genre: null })
}
}
}, 1000)
})
}).then((res) => {
let tmp = JSON.parse(localStorage['currentVideoInfo']);
window.clearInterval(timerId1);
tmp = {
id: this.props.videoId.toString(),
artist: res.artist,
genre: res.genre
}
localStorage.setItem('currentVideoInfo', JSON.stringify(tmp));
})
.finally(() => this.setState({ isLoaded: true }));
}
// shouldComponentUpdate(nextProps, nextState) {
// }
// componentDidUpdate(prevProps, prevState) {
// }
// componentWillUnmount() {
// }
render() {
const { wikidata, wikipedia, musicbrainz, error, isWikiLoaded, isWDataLoaded, isMBLoaded } = this.state;
const ISO_8601toYYYY = this.ISO_8601toYYYY;
if (error) {
return <React.Fragment>Error: {error.message}</React.Fragment>;
} else if (!this.state.isLoaded) {
return <Col className="text-center">
<div className="lds-ellipsis"><div></div><div></div><div></div><div></div></div>
</Col>;
} else {
return (
<Table size="sm">
<tbody>
{isWikiLoaded &&
this.state.wikipediakeys.map(key => (
<tr key={key}>
<td>{key}</td>
<td>{wikipedia.info[key].toLocaleString()}</td>
</tr>
))
}
{isWDataLoaded &&
this.state.wikidatakeys.map(key => (
<tr key={key[0]}>
<td>{`${key[0]} -
${key[1].labels.en ? key[1].labels.en.value : ""} `}
</td>
<td>{
typeof wikidata.claims[key[0]][0].mainsnak.datavalue.value === "string" ? // wikidata external
this.getExternalLink(key[0], wikidata.claims[key[0]][0].mainsnak.datavalue.value) :
typeof wikidata.claims[key[0]][0].mainsnak.datavalue.value.id === "string" ? // wikidata Q
<><a target="_blank" rel="noopener noreferrer" href={`https://www.wikidata.org/wiki/${wikidata.claims[key[0]][0].mainsnak.datavalue.value.id}`}>
{`${this.state.wikidataQ[wikidata.claims[key[0]][0].mainsnak.datavalue.value.id].labels.en ?
this.state.wikidataQ[wikidata.claims[key[0]][0].mainsnak.datavalue.value.id].labels.en.value :
"??"}`}
</a> {`(${wikidata.claims[key[0]][0].mainsnak.datavalue.value.id})`}</> :
typeof wikidata.claims[key[0]][0].mainsnak.datavalue.value.time === "string" ?
ISO_8601toYYYY(wikidata.claims[key[0]][0].mainsnak.datavalue.value.time.toString()) :
""
}</td>
</tr>
))
}
{isMBLoaded &&
musicbrainz.relations.map((relation, i) => {
if (relation["target-type"] === "artist")
return (<tr key={i}>
<td>{relation.type}</td>
<td>{relation.artist.name}</td>
</tr>);
else if (relation["target-type"] === "url")
return (<tr key={i}>
<td>{relation.type}</td>
<td><a target="_blank" rel="noopener noreferrer" href={relation.url.resource}>{relation.type}</a></td>
</tr>)
else
return ("");
})
}
</tbody>
</Table>
);
}
}
}
export default Wikipedia;
-64
View File
@@ -1,64 +0,0 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { Row, Col, Button, Collapse } from 'react-bootstrap';
// import Random from './reccomenders/Random';
// import Related from './reccomenders/Related';
// and so on..
class Suggestion extends Component {
constructor(props) {
super(props);
}
componentWillMount() {
}
componentDidMount() {
}
componentWillReceiveProps(nextProps) {
}
shouldComponentUpdate(nextProps, nextState) {
}
componentWillUpdate(nextProps, nextState) {
}
componentDidUpdate(prevProps, prevState) {
}
componentWillUnmount() {
}
render() {
let a =['','','','','','','','','','',''];
return (
<React.Fragment>
<Col xs="10">
<Row>
{a.map((x,i) => <Col xs={{'span': 10, 'offset': 1}} className="mt-1 border border-danger">- video {i}</Col>)}
</Row>
</Col>
<Col className="mt-1 mr-2 border border-danger">
{a.map((x,i) => i)}
?? box utile o superfluo?
</Col>
</React.Fragment>
);
}
}
Suggestion.propTypes = {
};
export default Suggestion;
-94
View File
@@ -1,94 +0,0 @@
import React, { Component } from 'react';
// import PropTypes from 'prop-types';
import wikijs from 'wikijs';
class Wikipedia extends Component {
constructor(props) {
super(props);
this.state = {
data: {},
infobox: {},
error: null,
isLoaded: false
};
this.getWikiBox = this.getWikiBox.bind(this);
this.raffina = this.raffina.bind(this);
}
getWikiBox(name){
wikijs()
.page(name)
.then(page => page.fullInfo())
.then(infobox => console.log('infobox', infobox)
//this.setState({ infobox })
);
//console.log('infobox', this.state.infobox)
}
raffina(results){
console.log('raffina',results);
return results[0];
}
// componentWillMount() {
// }
componentWillMount() {
wikijs().find(this.props.query, this.raffina).then(data => {
this.setState({
data,
isLoaded: true
});
console.log(data.html());
//this.getWikiBox(data.results[0]);
}, error => {
console.error(error);
this.setState({
isLoaded: true,
error
});
});
}
// componentWillReceiveProps(nextProps) {
// }
// shouldComponentUpdate(nextProps, nextState) {
// }
// componentWillUpdate(nextProps, nextState) {
// }
// componentDidUpdate(prevProps, prevState) {
// }
// componentWillUnmount() {
// }
render() {
const { data, error, isLoaded } = this.state;
if (error) {
return (
<React.Fragment>
Error: {error.message} -- Cannot get {error.config.url}
</React.Fragment>
);
} else if (!isLoaded) {
return <React.Fragment>Loading...</React.Fragment>;
} else {
return (
<div>
{/* <p>risultato ricerca wikipedia: { data.results[0] }</p> */}
</div>
);
}
}
}
export default Wikipedia;
+18
View File
@@ -0,0 +1,18 @@
.person-wrapper {
margin-bottom: 2em;
}
.profile-pic {
width: 50%;
}
.btn-primary {
background-image: none;
border: none;
border-radius: 0;
background-color: #2892D7;
}
h3 {
margin-bottom: 1em;
}
+74 -9
View File
@@ -1,16 +1,81 @@
.upperSection{
background-color:#000000;
display: fixed;
}
/* .sticky{
position: -webkit-sticky;
position: sticky;
top: 0;
z-index: 1040;
} */
.blackBg{
background-color:#000000;
}
.downSection{
overflow-y: auto;
overflow-x: hidden;
/* margin-top: 1vh; */
}
}
.suggItem:hover{
background-color: #213241;
text-decoration: none;
/* color: */
}
.suggItem:hover h5{
color: #DF691A;
}
.suggItem p{
color: #FFFFFF;
}
/* LOADING ANIMATION */
.lds-ellipsis {
display: inline-block;
position: relative;
width: 64px;
height: 64px;
}
.lds-ellipsis div {
position: absolute;
top: 27px;
width: 11px;
height: 11px;
border-radius: 50%;
background: #fff;
animation-timing-function: cubic-bezier(0, 1, 1, 0);
}
.lds-ellipsis div:nth-child(1) {
left: 6px;
animation: lds-ellipsis1 0.6s infinite;
}
.lds-ellipsis div:nth-child(2) {
left: 6px;
animation: lds-ellipsis2 0.6s infinite;
}
.lds-ellipsis div:nth-child(3) {
left: 26px;
animation: lds-ellipsis2 0.6s infinite;
}
.lds-ellipsis div:nth-child(4) {
left: 45px;
animation: lds-ellipsis3 0.6s infinite;
}
@keyframes lds-ellipsis1 {
0% {
transform: scale(0);
}
100% {
transform: scale(1);
}
}
@keyframes lds-ellipsis3 {
0% {
transform: scale(1);
}
100% {
transform: scale(0);
}
}
@keyframes lds-ellipsis2 {
0% {
transform: translate(0, 0);
}
100% {
transform: translate(19px, 0);
}
}
+6 -1
View File
@@ -6,13 +6,18 @@
.navbar-light .navbar-nav .nav-link:hover, .navbar-light .navbar-nav .nav-link:focus {
color: rgba(255,255,255);
}
.navbar-light .navbar-nav .nav-link:hover, .navbar-light {
color: rgba(248, 245, 245, 0.808);
}
}
.navbar-light .navbar-nav .show > .nav-link, .navbar-light .navbar-nav .active > .nav-link, .navbar-light .navbar-nav .nav-link.show, .navbar-light .navbar-nav .nav-link.active {
color: rgb(248, 245, 245, 0,808);
}
+10
View File
@@ -0,0 +1,10 @@
// Required
@import "~bootstrap/scss/functions";
@import "~bootstrap/scss/variables";
@import "~bootstrap/scss/mixins";
// To extend
@import '~bootstrap/scss/utilities/text';
a[aria-selected=false]{
@extend .text-white;
}
+8
View File
@@ -0,0 +1,8 @@
a[target=_blank]{
background: transparent url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAYAAABWdVznAAAAXklEQVQoka2QwQ3AMAwCs1N28k7eiZ3oI7IcU6efBomXOREyxhUZ2brTdNAcVB2BaJgCVcDAalJLXsB+iLAjm1pAwzHWHD3gWMcMg/ERMjKfFOHVqMEGqEM/gKP/6gE2f+h+Z5P45wAAAABJRU5ErkJggg==') center right no-repeat;
padding-right: 14px;
}
tbody a{
color: #DF691A !important;
}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+17 -3
View File
@@ -1,10 +1,24 @@
// import 'bootstrap/dist/css/bootstrap.css';
// import './css/bootswatch/darkly/bootstrap.min.css'
import './css/bootswatch/superhero/bootstrap.min.css'
import 'react-app-polyfill/ie9';
//import '@babel/polyfill';
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import registerServiceWorker from './registerServiceWorker';
import { HashRouter, BrowserRouter } from 'react-router-dom';
import { HashRouter, BrowserRouter } from 'react-router-dom'; // eslint-disable-line
ReactDOM.render(<HashRouter><App /></HashRouter>, document.getElementById('root'));
registerServiceWorker();
localStorage['lastWatched'] ? function () { }() : localStorage.setItem('lastWatched', JSON.stringify([]))
localStorage['first'] ? function () { }() : localStorage.setItem('first', JSON.stringify([]))
localStorage['prevId'] ? function () { }() : localStorage.setItem('prevId', '1')
localStorage['lastId'] ? function () { }() : localStorage.setItem('lastId', '0J2QdDbelmY')
localStorage['currentVideoInfo'] ? function () { }() : localStorage.setItem('currentVideoInfo', JSON.stringify({
'id': '0J2QdDbelmY',
'artist': 'Seven Nation Army',
'genre': 'Rock'
}));
localStorage['lastTopic'] ? function () { }() : localStorage.setItem('lastTopic', '/m/04rlf');
ReactDOM.render(<BrowserRouter><App /></BrowserRouter>, document.getElementById('root'));
registerServiceWorker();
+74 -47
View File
@@ -1,37 +1,38 @@
import React, { Component } from 'react';
import 'bootstrap/dist/css/bootstrap.css';
import axios from "axios";
import createpageToken from 'youtube-page-token';
import {Card} from "react-bootstrap";
import {Link} from "react-router-dom";
import { Card, Col, Media } from "react-bootstrap";
import { Link } from "react-router-dom";
import moment from "moment";
class RecommenderRandom extends Component{
constructor(props){
super(props);
this.state={
items : [],
};
class RecommenderRandom extends Component {
state = {
items: [],
}
componentDidMount(){
ISO_8601parse(a) {
return moment(a, moment.ISO_8601).format('ddd, DD/MM/YYYY hh:mm:ss');
}
componentDidMount() {
let pos = Math.floor(Math.random() * 500);
let pageToken = createpageToken(pos); /* createPageToken(pos) restituisce il pageToken relativo al numero
di pagina che gli viene passato(in questo caso pos) che verrai poi usato
come paramentro da axios per ottenere una determinata pagina di risultati */
di pagina che gli viene passato(in questo caso pos) che verrai poi usato
come parametro da axios per ottenere una determinata pagina di risultati */
axios.get("https://www.googleapis.com/youtube/v3/search", {
params: {
'part': 'snippet',
'topicId' : '/m/04rlf', //Filtro per la musica.In questo caso il valore è quello della parent directory
'type' : 'video', //che contiene tutti i generi di musica
'topicId': '/m/04rlf', //Filtro per la musica.In questo caso il valore è quello della parent directory
'type': 'video', //che contiene tutti i generi di musica
'maxResults': '21',
'pageToken' : pageToken,
'pageToken': pageToken,
'key': 'AIzaSyA21Odw9UdfiOJnxvA_eDfOW0OxuUfHKig'
}
}).then(
response => {
this.setState({
items : response.data.items,
items: response.data.items,
})
},
error => {
@@ -40,38 +41,64 @@ class RecommenderRandom extends Component{
)
}
render(){
render() {
const ISO_8601parse = this.ISO_8601parse;
if (window.innerWidth < 992) {
return (<React.Fragment>
{this.state.items.map(function (item, index) {
return (
<Col key={index} md='4'>
<Card>
<Link
to={{
pathname: '/video/' + item.id.videoId,
search: "?ref=Random"
}}
>
<Card.Img
src={item.snippet.thumbnails.high.url}
alt={'Thumbnail of ' + item.snippet.title}
/>
<Card.ImgOverlay>
<Card.Title className="bg-dark d-inline text-white">
{item.snippet.title}<br />
{item.snippet.channelTitle} - {ISO_8601parse(item.snippet.publishedAt)}<br />
<span>Reccomender: Random</span>
</Card.Title>
</Card.ImgOverlay>
</Link>
</Card>
</Col>
)
})}
</React.Fragment>);
}else{
return (
<div style={{width: '100rem',height:'100rem'}}>
{this.state.items.map(function (item,index) {
return(
<div key={index} style={{margin: '1rem',float: 'left'}}>
<Card>
<Link
to={{
pathname: '/video/' + item.id.videoId,
}}
>
<Card.Img
src={item.snippet.thumbnails.high.url}
alt={'Thumbnail of ' + item.title}
style={{width:'30rem',height:'20rem'}}
/>
<Card.ImgOverlay>
<Card.Title className="bg-dark d-inline text-white">
{item.snippet.title}<br/>
{item.snippet.channelTitle} - {item.snippet.publishedAt.replace(/(\d+)-(\d+)-(\d+)T(\d+):(\d+):(\d+).000Z/,function(match,p1,p2,p3,p4,p5,p6){
return(p3+'/'+p2+'/'+p1+' at '+p4+':'+p5+':'+p6); //Funzione che trasforma la data e ora di pubblicazione dal formato ISO nel normale formato da cristiani
})}
</Card.Title>
</Card.ImgOverlay>
</Link>
</Card>
</div>
)
})}
</div>
<ul className="list-unstyled">{
this.state.items.map(item => (
<Link
key={item.id.videoId}
as="li" className="media mx-5 my-1 suggItem"
to={{
pathname: `/video/${item.id.videoId}`,
search: '?ref=Random'
}}>
<img
width={240}
height={120}
className="align-self-center mr-3 img-fluid"
src={item.snippet.thumbnails.high.url}
alt={'Thumbnail of ' + item.snippet.title}
/>
<Media.Body>
<h5>{item.snippet.title}</h5>
<p> {item.snippet.channelTitle} - {ISO_8601parse(item.snippet.publishedAt)}</p>
<p>Reccomender: Random</p>
</Media.Body>
</Link>
))
}</ul>
);
}
}}
}
export default RecommenderRandom;
+133
View File
@@ -0,0 +1,133 @@
import React from 'react';
import { Col, Card, Media } from 'react-bootstrap'; // eslint-disable-line no-unused-vars
import axios from 'axios'; // eslint-disable-line no-unused-vars
import { Link } from 'react-router-dom'; // eslint-disable-line no-unused-vars
/*eslint no-console: ["error", { allow: ["warn", "error", "info"] }] */
import moment from "moment";
class Recent extends React.Component {
state = {
error: null,
isLoaded: false,
items: [],
headers: null
};
ISO_8601parse(a) {
return moment(a, moment.ISO_8601).format('ddd, DD/MM/YYYY hh:mm:ss');
}
componentDidMount() {
this.getThumbnailsNames(JSON.parse(localStorage.getItem('lastWatched')));
}
// METODI
getThumbnailsNames(recentVideoList) {
let idToLookFor = recentVideoList.map(item => item.id);
axios.get('https://www.googleapis.com/youtube/v3/videos', {
params: {
part: 'snippet',
id: idToLookFor.toString(),
key: 'AIzaSyA21Odw9UdfiOJnxvA_eDfOW0OxuUfHKig',
fields: 'items(id,snippet/thumbnails/medium,snippet/title)'
}
})
.then(
res1 => {
res1.data.items.map(resCurrentValue =>
Object.defineProperties(
recentVideoList.find(videoItem => {
return videoItem.id === resCurrentValue.id;
}),
{
'thumbnail':
{ value: resCurrentValue.snippet.thumbnails.medium.url },
'name':
{ value: resCurrentValue.snippet.title }
})
);
this.setState({
isLoaded: true,
items: recentVideoList
});
})
}
render() {
const { error, isLoaded, items } = this.state;
if (error) {
return (
<React.Fragment>
Error: {error.message} -- Cannot get {error.config.url}
</React.Fragment>
);
} else if (!isLoaded) {
return <Col className="text-center">
<div className="lds-ellipsis"><div></div><div></div><div></div><div></div></div>
</Col>;
} else {
if (window.innerWidth < 992) {
return (
<React.Fragment>
{items.map(item => (
<Col key={item.id} md="4">
<Card >
<Link
to={{
pathname: '/video/' + item.id,
search: "?ref=Recent"
}}
>
<Card.Img
src={item.thumbnail}
alt={'Thumbnail of ' + item.name}
/>
<Card.ImgOverlay>
<Card.Title className="bg-dark d-inline text-white">
{item.name}<br />
<span>Ultima volta visto: {this.ISO_8601parse(item.lastWatched)} </span><br />
<span>Reccomender: Recent</span>
</Card.Title>
</Card.ImgOverlay>
</Link>
</Card>
</Col>))}
</React.Fragment>
);
}else{
return (
<ul className="list-unstyled">{
this.state.items.map(item => (
<Link
key={item.name}
as="li" className="media mx-5 my-1 suggItem"
to={{
pathname: `/video/${item.name}`,
search: '?ref=Recent'
}}>
<img
width={240}
height={120}
className="align-self-center mr-3 img-fluid"
src={item.thumbnail}
alt={'Thumbnail of ' + item.name}
/>
<Media.Body>
<h5>{item.name}</h5><br />
<p>Ultima volta visto: {this.ISO_8601parse(item.lastWatched)} </p><br />
<p>Reccomender: Recent</p>
</Media.Body>
</Link>
))
}</ul>
);
}}
}
}
export default Recent;
+76 -38
View File
@@ -1,33 +1,28 @@
import React, { Component } from 'react';
import axios from 'axios';
import {Link} from "react-router-dom";
import {Card} from "react-bootstrap";
import { Link } from "react-router-dom";
import { Card, Col, Media } from "react-bootstrap";
import moment from "moment";
class Related extends Component {
constructor(props) {
super(props);
this.state={
videoId : '0J2QdDbelmY',
items : []
}
state = {
videoId: localStorage['lastId'],
items: []
}
componentWillMount() {
if(this.props.match.params.id){
if(this.props.match.params.id === this.state.videoId){}
else{
this.setState({videoId: this.props.match.params.id});
}
}
ISO_8601parse(a) {
return moment(a, moment.ISO_8601).format('ddd, DD/MM/YYYY hh:mm:ss');
}
componentDidMount() {
axios.get("https://www.googleapis.com/youtube/v3/search",{
params:{
axios.get("https://www.googleapis.com/youtube/v3/search", {
params: {
'part': 'snippet',
'relatedToVideoId' : this.state.videoId,
'type' : 'video',
'maxResults' : '21',
'relatedToVideoId': this.props.match.params.id,
'type': 'video',
'maxResults': '22',
'key': 'AIzaSyA21Odw9UdfiOJnxvA_eDfOW0OxuUfHKig'
}
}).then(
@@ -35,7 +30,6 @@ class Related extends Component {
this.setState({
items: response.data.items,
})
console.log(response);
},
error => {
console.error(error);
@@ -43,46 +37,90 @@ class Related extends Component {
)
}
componentWillUpdate(nextProps, nextState) {
if(nextProps.match.params.id === this.state.videoId){}
else if( nextProps.match.params.id){
this.setState({videoId: nextProps.match.params.id});
componentDidUpdate(prevProps, prevState) {
if (prevProps.match.params.id !== this.props.match.params.id) {
axios.get("https://www.googleapis.com/youtube/v3/search", {
params: {
'part': 'snippet',
'relatedToVideoId': this.props.match.params.id,
'type': 'video',
'maxResults': '22',
'key': 'AIzaSyA21Odw9UdfiOJnxvA_eDfOW0OxuUfHKig'
}
}).then(
response => {
this.setState({
items: response.data.items,
})
},
error => {
console.error(error);
}
)
}
}
render() {
const ISO_8601parse = this.ISO_8601parse;
if (window.innerWidth < 992) {
return (
<div style={{width: '100rem',height:'100rem'}}>
{this.state.items.map(function (item,index) {
return(
<div key={index} style={{margin: '1rem',float: 'left'}}>
<React.Fragment>
{this.state.items.map(function (item, index) {
return (
<Col key={index} md="4">
<Card>
<Link
to={{
pathname: '/video/' + item.id.videoId,
search: "?ref=Related"
}}
>
<Card.Img
src={item.snippet.thumbnails.high.url}
alt={'Thumbnail of ' + item.title}
style={{width:'30rem',height:'20rem'}}
alt={'Thumbnail of ' + item.snippet.title}
/>
<Card.ImgOverlay>
<Card.Title className="bg-dark d-inline text-white">
{item.snippet.title}<br/>
{item.snippet.channelTitle} - {item.snippet.publishedAt.replace(/(\d+)-(\d+)-(\d+)T(\d+):(\d+):(\d+).000Z/,function(match,p1,p2,p3,p4,p5,p6){
return(p3+'/'+p2+'/'+p1+' at '+p4+':'+p5+':'+p6); //Funzione che trasforma la data e ora di pubblicazione dal formato ISO nel normale formato da cristiani
})}
{item.snippet.title}<br />
{item.snippet.channelTitle} - {ISO_8601parse(item.snippet.publishedAt)}<br />
<span>Reccomender: Related</span>
</Card.Title>
</Card.ImgOverlay>
</Link>
</Card>
</div>
</Col>
)
})}
</div>
</React.Fragment>
);
}
}else{
return (
<ul className="list-unstyled">{
this.state.items.map(item => (
<Link
key={item.id.videoId}
as="li" className="media mx-5 my-1 suggItem"
to={{
pathname: `/video/${item.id.videoId}`,
search: '?ref=Related'
}}>
<img
width={240}
height={120}
className="align-self-center mr-3 img-fluid"
src={item.snippet.thumbnails.high.url}
alt={'Thumbnail of ' + item.snippet.title}
/>
<Media.Body>
<h5>{item.snippet.title}</h5>
<p> {item.snippet.channelTitle} - {ISO_8601parse(item.snippet.publishedAt)}</p>
<p>Reccomender: Related</p>
</Media.Body>
</Link>
))
}</ul>
);
}}
}
Related.propTypes = {
+170
View File
@@ -0,0 +1,170 @@
import React from 'react';
import axios from 'axios';
import { Link } from 'react-router-dom';
import { Col, Media, Card } from 'react-bootstrap';
class Search extends React.Component {
state = {
query: '',
isLoaded: false,
error: null,
res: null
}
// static getDerivedStateFromProps(nextProps, prevState) {
// }
componentDidMount() {
if (new RegExp("[0-9A-Za-z_-]{10}[048AEIMQUYcgkosw]").test(this.props.match.params.query.toString()))
axios.get('https://www.googleapis.com/youtube/v3/videos', {
params: {
'part': 'id',
'id': this.props.match.params.query,
'key': 'AIzaSyA21Odw9UdfiOJnxvA_eDfOW0OxuUfHKig',
'field': 'pageInfo/resultsPerPage'
}
}).then(
res => {
res.data.pageInfo.resultsPerPage ?
this.props.history.push('/video/' + this.props.match.params.query) :
this.youtubeSearch()
},
error => {
console.error(error)
}
)
else
this.youtubeSearch();
}
// shouldComponentUpdate(nextProps, nextState) {
// }
componentDidUpdate(prevProps, prevState) {
if (prevProps.match.params.query !== this.props.match.params.query) {
this.setState({ isLoaded: false })
if (new RegExp("[0-9A-Za-z_-]{10}[048AEIMQUYcgkosw]").test(this.props.match.params.query.toString()))
axios.get('https://www.googleapis.com/youtube/v3/videos', {
params: {
'part': 'id',
'id': this.props.match.params.query,
'key': 'AIzaSyA21Odw9UdfiOJnxvA_eDfOW0OxuUfHKig',
'field': 'pageInfo/resultsPerPage'
}
}).then(
res => {
res.data.pageInfo.resultsPerPage ?
this.props.history.push('/video/' + this.props.match.params.query) :
this.youtubeSearch()
},
error => {
console.error(error)
this.setState({
error
})
}
)
else
this.youtubeSearch();
}
}
// componentWillUnmount() {
// }
youtubeSearch() {
return axios.get('https://www.googleapis.com/youtube/v3/search', {
params: {
'part': 'snippet',
'q': this.props.match.params.query,
'videoEmbeddable': 'true',
'type': 'video',
'maxResults': 30,
'topicId': '/m/04rlf, /m/02jjt',
'key': 'AIzaSyA21Odw9UdfiOJnxvA_eDfOW0OxuUfHKig'
}
}).then(
res =>
this.setState({
res: res.data,
isLoaded: true
})
,
error => this.setState({
error
})
)
}
render() {
if (this.state.error) {
return <React.Fragment>{this.state.error.message} -- {this.state.error.response.data.error.errors[0].reason} </React.Fragment>;
} else if (!this.state.isLoaded) {
return <Col className="text-center">
<div className="lds-ellipsis"><div></div><div></div><div></div><div></div></div>
</Col>;
} else {
if (window.innerWidth > 992) {
return (
<ul className="list-unstyled">{
this.state.res.items.map(item => (
<Link
key={item.id.videoId}
as="li" className="media mx-5 my-1 suggItem"
to={{
pathname: '/video/' + item.id.videoId,
search: "?ref=Search"
}}>
<img
width={240}
height={120}
className="align-self-center mr-3"
src={item.snippet.thumbnails.medium.url}
alt={'Thumbnail of ' + item.snippet.title}
/>
<Media.Body>
<h5>{item.snippet.title}</h5>
<p>{item.snippet.description}</p>
<p>Reccomender: Search</p>
</Media.Body>
</Link>
))
}</ul>
);
}else{
return (
<React.Fragment>
{this.state.res.items.map(item => (<Col md="4">
<Card key={item.id.videoId}>
<Link
to={{
pathname: '/video/' + item.id.videoId,
search: "?ref=Search"
}}
>
<Card.Img
src={item.snippet.thumbnails.medium.url}
alt={'Thumbnail of ' + item.snippet.title}
/>
<Card.ImgOverlay>
<Card.Title className="bg-dark d-inline text-white">
<p>{item.snippet.title}</p>
<p className='small'>{item.lastSelected}</p>
<p className='small'>Reccomender: Search</p>
</Card.Title>
</Card.ImgOverlay>
</Link>
</Card>
</Col>))}
</React.Fragment>
);
}}
}
}
export default Search;
+144 -19
View File
@@ -1,22 +1,147 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import React from 'react';
import { Col, Card, Media } from 'react-bootstrap'; // eslint-disable-line no-unused-vars
import axios from 'axios'; // eslint-disable-line no-unused-vars
import { Link } from 'react-router-dom'; // eslint-disable-line no-unused-vars
/*eslint no-console: ["error", { allow: ["warn", "error", "info"] }] */
class fvitali extends React.Component {
state = {
error: null,
isLoaded: false,
items: [],
};
componentDidMount() {
axios.get('http://site1825.tw.cs.unibo.it/TW/globpop', {
'params': {
'id': this.props.match.params.id //se 'id'=>'Id', allora tutti video random, sennò anche per genere simile
}
}).then(
response => {
this.getThumbnailsNames(response.data.recommended);
},
error => {
console.error(error);
this.setState({
isLoaded: true,
error
});
}
);
}
// METODI
getThumbnailsNames(reccomended) {
let idToLookFor = reccomended.map(item => item.videoID);
axios.get('https://www.googleapis.com/youtube/v3/videos', {
params: {
part: 'snippet',
id: idToLookFor.toString(),
key: 'AIzaSyA21Odw9UdfiOJnxvA_eDfOW0OxuUfHKig',
fields: 'items(id,snippet/thumbnails/medium,snippet/title)'
}
})
.then(
res1 => {
res1.data.items.map(resCurrentValue =>
Object.defineProperties(
reccomended.find(videoItem => {
return videoItem.videoID === resCurrentValue.id;
}),
{
'thumbnail':
{ value: resCurrentValue.snippet.thumbnails.medium.url },
'name':
{ value: resCurrentValue.snippet.title }
})
);
this.setState({
isLoaded: true,
items: reccomended
});
})
}
render() {
const { error, isLoaded, items } = this.state;
if (error) {
return (
<React.Fragment>
Error: {error.message} -- Cannot get {error.config.url}
</React.Fragment>
);
} else if (!isLoaded) {
return (
<Col className="text-center">
<div className="lds-ellipsis"><div></div><div></div><div></div><div></div></div>
</Col>
);
} else {
if (window.innerWidth < 992) {
return (
<React.Fragment>
{items.map(item => (<Col md="4">
<Card key={item.videoID}>
<Link
to={{
pathname: '/video/' + item.videoID,
search: "?ref=fvitali"
}}
>
<Card.Img
src={item.thumbnail}
alt={'Thumbnail of ' + item.name}
/>
<Card.ImgOverlay>
<Card.Title className="bg-dark d-inline text-white">
<span>{item.name}</span> <br/>
<span>Reccomender: fvitali</span>
</Card.Title>
</Card.ImgOverlay>
</Link>
</Card>
</Col>))}
</React.Fragment>
);
} else {
return (
<ul className="list-unstyled">{
items.map(item => (
<Link
key={item.videoID}
as="li" className="media mx-5 my-1 suggItem"
to={{
pathname: `/video/${item.videoID}`,
search: '?ref=fvitali'
}}>
<img
width={240}
height={120}
className="align-self-center mr-3 img-fluid"
src={item.thumbnail}
alt={'Thumbnail of ' + item.name}
/>
<Media.Body>
<h5>{item.name}</h5>
<p>Reccomender: fvitali</p>
</Media.Body>
</Link>
))
}</ul>
);
}
}
}
}
axios.get('http://site1825.tw.cs.unibo.it/TW/globpop',
{
id: videoID // variabile con videoid
})
.then(
response => { // notazione moderna javascript per indicare function(response){}
// function(response){} == response => {}
console.log(response);
console.info(response.data);
},
// Note: it's important to handle errors here
// instead of a catch() block so that we don't swallow
// exceptions from actual bugs in components.
error => {
console.error(error);
}
);
export default fvitali;
+104
View File
@@ -0,0 +1,104 @@
import React, { Component } from 'react';
import axios from 'axios';
import { Card, Col, Media } from "react-bootstrap";
import createpageToken from 'youtube-page-token';
import { Link } from "react-router-dom";
import moment from "moment";
class genreSimilarity extends Component {
state = {
items: []
};
ISO_8601parse(a) {
return moment(a, moment.ISO_8601).format('ddd, DD/MM/YYYY hh:mm:ss');
}
componentDidMount() {
let pos = Math.floor(Math.random() * 100);
let pageToken = createpageToken(pos);
axios.get("https://www.googleapis.com/youtube/v3/search", {
params: {
'part': 'snippet',
'topicId': localStorage['lastTopic'],
'type': 'video',
'maxResults': '21',
'order': 'viewCount',
'pageToken': pageToken,
'key': 'AIzaSyA21Odw9UdfiOJnxvA_eDfOW0OxuUfHKig'
}
}).then(
response => {
this.setState({
items: response.data.items,
})
},
error => {
console.error(error);
}
)
}
render() {
const ISO_8601parse = this.ISO_8601parse;
if (window.innerWidth < 992){
return (<React.Fragment>
{this.state.items.map(function (item, index) {
return (
<Col key={index} md='4'>
<Card>
<Link
to={{
pathname: '/video/' + item.id.videoId,
search: "?ref=genreSimilarity"
}}
>
<Card.Img
src={item.snippet.thumbnails.high.url}
alt={'Thumbnail of ' + item.snippet.title}
/>
<Card.ImgOverlay>
<Card.Title className="bg-dark d-inline text-white">
{item.snippet.title}<br />
{item.snippet.channelTitle}<br />
<span>Reccomender: similarityArtist</span>
</Card.Title>
</Card.ImgOverlay>
</Link>
</Card>
</Col>
)
})}
</React.Fragment>);
}else {
return (
<ul className="list-unstyled">{
this.state.items.map(item => (
<Link
key={item.id.videoId}
as="li" className="media mx-5 my-1 suggItem"
to={{
pathname: `/video/${item.id.videoId}`,
search: '?ref=genreSimilarity'
}}>
<img
width={240}
height={120}
className="align-self-center mr-3 img-fluid"
src={item.snippet.thumbnails.high.url}
alt={'Thumbnail of ' + item.snippet.title}
/>
<Media.Body>
<h5>{item.snippet.title}</h5>
<p> {item.snippet.channelTitle} - {ISO_8601parse(item.snippet.publishedAt)}</p>
<p>Reccomender: genreSimilarity</p>
</Media.Body>
</Link>
))
}</ul>
);
}}
}
export default genreSimilarity;
+140
View File
@@ -0,0 +1,140 @@
import React from 'react';
import { Col, Media, Card } from 'react-bootstrap'; // eslint-disable-line no-unused-vars
import axios from 'axios'; // eslint-disable-line no-unused-vars
import { Link } from 'react-router-dom'; // eslint-disable-line no-unused-vars
import moment from "moment";
var _ = require('lodash');
class popGlobaleAssoluta extends React.Component {
state = {
error: null,
isLoaded: false,
items: [],
headers: null
};
componentDidMount() {
// let listSiti = ['1828', '1829', '1838', '1839', '1846', '1822', '1847', '1831', '1827', '1848', '1824', '1830', '1836', '1850', '1849', '1851', '1861', '1823', '1863', '1834', '1904', '1906', '1901', '1862', '1859', '1841', '1905', '1864', '1840', '1860', '1858', '1835', '1911', '1913', '1826', '1855', '1907', '1912', '1903', '1915', '1854', '1910'];
let listSiti = ['1828', '1838','1839', '1846', '1831', '1827', '1823', '1863', '1834', '1901'];//, '1859', '1841', '1905', '1864', '1860', '1858', '1913', '1855', '1912', '1915', '1854', '1910'];
Promise.all(
listSiti.map(
el => fetch(`http://site${el}.tw.cs.unibo.it/globpop?id=${this.props.match.params.id}`)
.then(res =>
res.json()
)
)
).then(res => {
/* do something with res here... */
console.log(res)
let tmp = res.map(x => x.recommended);
//let tmp2 =tmp.filter(el=>el.length > 0).map(el => el.map(el1 => el1.videoID || el1.videoId) )
console.log(...tmp)
this.getThumbnailsNames(tmp);
});
}
// METODI
getThumbnailsNames(reccomended) {
console.log(reccomended)
let tmp2 =reccomended.filter(el=>el.length > 0).map(el => el.map(el1 => el1.videoID || el1.videoId) )
let idToLookFor =[].concat(...tmp2);
idToLookFor.filter(el=>el.videoID !== undefined || el.videoId !== undefined)
axios.get('https://www.googleapis.com/youtube/v3/videos', {
params: {
part: 'snippet',
id: _.uniq(idToLookFor).slice(0,50).toString(),
key: 'AIzaSyA21Odw9UdfiOJnxvA_eDfOW0OxuUfHKig',
fields: 'items(id,snippet/thumbnails/high,snippet/title)'
}
})
.then(
res1 => {
this.setState({
items: res1.data.items,
isLoaded: true,
});
console.log(res1)
})
}
render() {
const { error, isLoaded, items } = this.state;
if (error) {
return (
<React.Fragment>
Error: {error.message} -- Cannot get {error.config.url}
</React.Fragment>
);
} else if (!isLoaded) {
return (
<React.Fragment>
<Col>
<p className="text-center">Loading...</p>
</Col>
</React.Fragment>
);
} else { if (window.innerWidth < 992){
return (
<React.Fragment>
{items.map(item => (<Col md="4">
<Card>
<Link
to={{
pathname: '/video/' + item.videoID,
search: "?ref=popGlobaleAssoluta"
}}
>
<Card.Img
src={item.snippet.thumbnails.high.url}
alt={'Thumbnail of ' + item.snippet.title}
/>
<Card.ImgOverlay>
<Card.Title className="bg-dark d-inline text-white">
{item.snippet.title}<br />
{item.snippet.channelTitle}<br />
<span>Reccomender: popGlobaleAssoluta</span>
</Card.Title>
</Card.ImgOverlay>
</Link>
</Card>
</Col>))}
</React.Fragment>
);
}else {
return (
<ul className="list-unstyled">{
this.state.items.map(item => (
<Link
key={item.videoID}
as="li" className="media mx-5 my-1 suggItem"
to={{
pathname: `/video/${item.videoID}`,
search: '?ref=popGlobaleAssoluta'
}}>
<img
width={240}
height={120}
className="align-self-center mr-3 img-fluid"
src={item.snippet.thumbnails.high.url}
alt={'Thumbnail of ' + item.snippet.title}
/>
<Media.Body>
<h5>{item.snippet.title}</h5>
<p> {item.snippet.channelTitle}</p>
<p>Reccomender: popGlobaleAssoluta</p>
</Media.Body>
</Link>
))
}</ul>
);
}}
}
}
export default popGlobaleAssoluta;
+145
View File
@@ -0,0 +1,145 @@
import React from 'react';
import axios from 'axios';
import { Link } from 'react-router-dom';
import { Col, Media, Card } from 'react-bootstrap';
class popLocaleAss extends React.Component {
state = {
isLoaded: false,
error: null,
res: null
}
// static getDerivedStateFromProps(nextProps, prevState) {
// }
componentDidMount() {
axios
.get('http://site1854.tw.cs.unibo.it/videotrack')
.then(res => {
this.getThumbnailsNames(res.data.sort((a, b) => a.timesWatched - b.timesWatched).reverse());
}
, error => this.setState({ error }))
}
// shouldComponentUpdate(nextProps, nextState) {
// }
// componentDidUpdate(prevProps, prevState) {
// if (prevProps.match.params !== this.props.match.params) {
// this.setState({ isLoaded: false })
// }
// }
// componentWillUnmount() {
// }
getThumbnailsNames(reccomended) {
let idToLookFor = reccomended.map(item => item.id);
axios.get('https://www.googleapis.com/youtube/v3/videos', {
params: {
part: 'snippet',
id: idToLookFor.toString(),
key: 'AIzaSyA21Odw9UdfiOJnxvA_eDfOW0OxuUfHKig',
fields: 'items(id,snippet/thumbnails/medium,snippet/title,snippet/description)'
}
})
.then(
res1 => {
res1.data.items.map(resCurrentValue =>
Object.defineProperties(
reccomended.find(videoItem => {
return videoItem.id === resCurrentValue.id;
}),
{
'thumbnail':
{ value: resCurrentValue.snippet.thumbnails.medium.url },
'name':
{ value: resCurrentValue.snippet.title },
'description':
{ value: resCurrentValue.snippet.description }
})
);
this.setState({
isLoaded: true,
items: reccomended
});
})
}
render() {
if (this.state.error) {
return <React.Fragment>{this.state.error.message} -- {this.state.error.response.data.error.errors[0].reason} </React.Fragment>;
} else if (!this.state.isLoaded) {
return <Col className="text-center">
<div className="lds-ellipsis"><div></div><div></div><div></div><div></div></div>
</Col>;
} else {
if (window.innerWidth > 992){
return (
<ul className="list-unstyled">{
this.state.items.map(item => (
<Link
key={item.id.videoId}
as="li" className="media mx-5 my-1 suggItem"
to={{
pathname: '/video/' + item.id,
search: "?ref=popLocaleAss"
}}>
<img
width={240}
height={120}
className="align-self-center mr-3"
src={item.thumbnail}
alt={'Thumbnail of ' + item.name}
/>
<Media.Body>
<h5>{item.name}</h5>
<p>{item.description.substring(0, 200)}</p>
<p>Visto: {item.timesWatched} volt{item.timesWatched === 1 ?"a":"e"} </p>
<p> Reccomender: popLocaleAss </p>
</Media.Body>
</Link>
))
}</ul>
);
}else {return (<React.Fragment>
{this.state.items.map(function (item, index) {
return (
<Col key={index} md='4'>
<Card>
<Link
to={{
pathname: '/video/' + item.id,
search: "?ref=popLocaleAss"
}}
>
<Card.Img
src={item.thumbnail}
alt={'Thumbnail of ' + item.name}
/>
<Card.ImgOverlay>
<Card.Title className="bg-dark d-inline text-white">
{item.name}<br />
<span>Visto: {item.timesWatched} volt{item.timesWatched === 1 ?"a":"e"} </span><br />
<span> Reccomender: popLocaleAss </span>
</Card.Title>
</Card.ImgOverlay>
</Link>
</Card>
</Col>
)
})}
</React.Fragment>);
}}
}
}
export default popLocaleAss;
+149
View File
@@ -0,0 +1,149 @@
import React from 'react';
import { Col, Card, Media } from 'react-bootstrap'; // eslint-disable-line no-unused-vars
import axios from 'axios'; // eslint-disable-line no-unused-vars
import { Link } from 'react-router-dom'; // eslint-disable-line no-unused-vars
/*eslint no-console: ["error", { allow: ["warn", "error", "info"] }] */
import moment from "moment";
class PopRelLoc extends React.Component {
state = {
error: null,
isLoaded: false,
items: [],
headers: null
};
ISO_8601parse(a) {
return moment(a, moment.ISO_8601).format('ddd, DD/MM/YYYY hh:mm:ss');
}
componentDidMount() {
axios.get("http://site1854.tw.cs.unibo.it/videotrack",{
params: {
"prevId" : localStorage['lastId']
}
}).then(res=>{
this.getThumbnailsNames(res.data)
this.setState({
items : res.data
})
},
error=>console.error(error))
}
// METODI
getThumbnailsNames(listVideo) {
let idToLookFor = listVideo.map(item => item.id);
axios.get('https://www.googleapis.com/youtube/v3/videos', {
params: {
part: 'snippet',
id: idToLookFor.toString(),
key: 'AIzaSyA21Odw9UdfiOJnxvA_eDfOW0OxuUfHKig',
fields: 'items(id,snippet/thumbnails/medium,snippet/title)'
}
})
.then(
res1 => {
res1.data.items.map(resCurrentValue =>
Object.defineProperties(
listVideo.find(videoItem => {
return videoItem.id === resCurrentValue.id;
}),
{
'thumbnail':
{ value: resCurrentValue.snippet.thumbnails.medium.url },
'name':
{ value: resCurrentValue.snippet.title }
})
);
this.setState({
isLoaded: true,
items: listVideo
});
})
}
render() {
const { error, isLoaded, items } = this.state;
if (error) {
return (
<React.Fragment>
Error: {error.message} -- Cannot get {error.config.url}
</React.Fragment>
);
} else if (!isLoaded) {
return (
<React.Fragment>
<Col>
<p className="text-center">Loading...</p>
</Col>
</React.Fragment>
);
} else {
if (window.innerWidth < 992) {
return (
<React.Fragment>
{items.map(item => (
<Col md="4">
<Card key={item.id}>
<Link
to={{
pathname: '/video/' + item.id,
search: "?ref=popRelLoc"
}}
>
<Card.Img
src={item.thumbnail}
alt={'Thumbnail of ' + item.name}
/>
<Card.ImgOverlay>
<Card.Title className="bg-dark d-inline text-white">
{item.name}<br />
<span>Visto: {item.timesWatched} volt{item.timesWatched === 1 ?"a":"e"} </span><br />
<span> Reccomender: popRelLoc </span>
</Card.Title>
</Card.ImgOverlay>
</Link>
</Card>
</Col>))}
</React.Fragment>
);
}else{
return (
<ul className="list-unstyled">{
this.state.items.map(item => (
<Link
key={item.id.videoId}
as="li" className="media mx-5 my-1 suggItem"
to={{
pathname: '/video/' + item.id,
search: "?ref=popRelLoc"
}}>
<img
width={240}
height={120}
className="align-self-center mr-3"
src={item.thumbnail}
alt={'Thumbnail of ' + item.name}
/>
<Media.Body>
<h5>{item.name}</h5>
<p>Visto: {item.timesWatched} volt{item.timesWatched === 1 ?"a":"e"} </p>
<p> Reccomender: popRelLoc </p>
</Media.Body>
</Link>
))
}</ul>
);
}}
}
}
export default PopRelLoc;
+148
View File
@@ -0,0 +1,148 @@
import React from 'react';
import axios from 'axios';
import { Link } from 'react-router-dom';
import { Col, Media, Card } from 'react-bootstrap';
class similarityArtist extends React.Component {
state = {
isLoaded: false,
error: null,
res: null
}
// static getDerivedStateFromProps(nextProps, prevState) {
// }
componentDidMount() {
let timerId1;
let promise = new Promise((resolve, reject) => {
timerId1 = window.setInterval(() => {
if (localStorage['lastId'] === JSON.parse(localStorage['currentVideoInfo']).id)
resolve(true)
}, 1000)
})
promise.then(() => {
window.clearInterval(timerId1);
this.setState({ artist: JSON.parse(localStorage['currentVideoInfo']).artist })
axios.get('https://www.googleapis.com/youtube/v3/search', {
params: {
'part': 'snippet',
'q': JSON.parse(localStorage['currentVideoInfo']).artist,
'videoEmbeddable': 'true',
'type': 'video',
'maxResults': 15,
'topicId': '/m/04rlf, /m/02jjt',
'key': 'AIzaSyA21Odw9UdfiOJnxvA_eDfOW0OxuUfHKig'
}
}).then(
res => {
let j = res.data.items.findIndex(el => el.id.videoId === localStorage['lastId'])
if (j >= 0)
res.data.items.splice(j, 1);
this.setState({
res: res.data,
isLoaded: true
})
console.log(res);
}
,
error => this.setState({
error
})
)
})
}
// shouldComponentUpdate(nextProps, nextState) {
// }
// componentDidUpdate(prevProps, prevState) {
// if (prevProps.match.params !== this.props.match.params) {
// this.setState({ isLoaded: false })
// }
// }
// componentWillUnmount() {
// }
render() {
if (this.state.error) {
return <React.Fragment>{this.state.error.message} -- {this.state.error.response.data.error.errors[0].reason} </React.Fragment>;
} else if (!this.state.isLoaded) {
return <Col className="text-center">
<div className="lds-ellipsis"><div></div><div></div><div></div><div></div></div>
</Col>;
} else {
if (window.innerWidth > 992) {
return (
<>
<h4>Artist Similarity: {this.state.artist}</h4>
<ul className="list-unstyled">{
this.state.res.items.map(item => (
<Link
key={item.id.videoId}
as="li" className="media mx-5 my-1 suggItem"
to={{
pathname: '/video/' + item.id.videoId,
search: "?ref=similarityArtist"
}}>
<img
width={240}
height={120}
className="align-self-center mr-3"
src={item.snippet.thumbnails.medium.url}
alt={'Thumbnail of ' + item.snippet.title}
/>
<Media.Body>
<h5>{item.snippet.title}</h5>
<p>{item.snippet.description}</p>
<p>Reccomender: similarityArtist</p>
</Media.Body>
</Link>
))
}</ul>
</>
);
}else{
return (<React.Fragment>
{this.state.res.items.map(function (item, index) {
return (
<Col key={index} md='4'>
<Card>
<Link
to={{
pathname: '/video/' + item.id.videoId,
search: "?ref=similarityArtist"
}}
>
<Card.Img
src={item.snippet.thumbnails.high.url}
alt={'Thumbnail of ' + item.snippet.title}
/>
<Card.ImgOverlay>
<Card.Title className="bg-dark d-inline text-white">
{item.snippet.title}<br />
{item.snippet.channelTitle}<br />
<span>Reccomender: similarityArtist</span>
</Card.Title>
</Card.ImgOverlay>
</Link>
</Card>
</Col>
)
})}
</React.Fragment>);
}}
}
}
export default similarityArtist;
+8 -8
View File
@@ -10,12 +10,12 @@
const isLocalhost = Boolean(
window.location.hostname === 'localhost' ||
// [::1] is the IPv6 localhost address.
window.location.hostname === '[::1]' ||
// 127.0.0.1/8 is considered localhost for IPv4.
window.location.hostname.match(
/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
)
// [::1] is the IPv6 localhost address.
window.location.hostname === '[::1]' ||
// 127.0.0.1/8 is considered localhost for IPv4.
window.location.hostname.match(
/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
)
);
export default function register() {
@@ -30,7 +30,7 @@ export default function register() {
}
window.addEventListener('load', () => {
const swUrl = `${process.env.PUBLIC_URL?process.env.PUBLIC_URL:'http://localhost:8000'}/service-worker.js`;
const swUrl = `${process.env.PUBLIC_URL ? process.env.PUBLIC_URL : 'http://localhost:8000'}/service-worker.js`;
if (isLocalhost) {
// This is running on localhost. Lets check if a service worker still exists or not.
@@ -41,7 +41,7 @@ export default function register() {
navigator.serviceWorker.ready.then(() => {
console.log(
'This web app is being served cache-first by a service ' +
'worker. To learn more, visit https://goo.gl/SC7cgQ'
'worker. To learn more, visit https://goo.gl/SC7cgQ'
);
});
} else {