web -> desktop

This commit is contained in:
Alexey Kasyanchuk
2018-01-31 19:02:28 +03:00
parent 0e4888ab76
commit d8afce8964
95 changed files with 10679 additions and 1893 deletions

49
src/app/admin-page.js Normal file
View File

@ -0,0 +1,49 @@
import React from 'react';
import Page from './page';
import Footer from './footer';
import {Header} from './index-page'
import Toggle from 'material-ui/Toggle';
export default class AdminPage extends Page {
constructor(props) {
super(props)
this.setTitle('-=-= Some page =-=-');
this.options = {}
}
componentDidMount() {
this.loadSettings()
}
loadSettings() {
window.torrentSocket.emit('admin', window.customLoader((options) => {
this.options = options;
console.log(this.options)
this.forceUpdate();
}));
}
saveSettings() {
window.torrentSocket.emit('setAdmin', this.options)
this.forceUpdate()
}
render() {
return (
<div>
<Header />
<div className='column center w100p pad0-75'>
<Toggle
style={{marginTop: '10px'}}
label="Disable DHT scanning"
toggled={this.options.dhtDisabled}
thumbSwitchedStyle={{backgroundColor: 'red'}}
trackSwitchedStyle={{backgroundColor: '#ff9d9d'}}
onToggle={(e, checked) => {
this.options.dhtDisabled = checked
this.saveSettings()
}}
/>
<Footer />
</div>
</div>
);
}
}

24
src/app/app.css Normal file
View File

@ -0,0 +1,24 @@
.App {
text-align: center;
}
.App-logo {
animation: App-logo-spin infinite 20s linear;
height: 80px;
}
.App-header {
background-color: #222;
height: 150px;
padding: 20px;
color: white;
}
.App-intro {
font-size: large;
}
@keyframes App-logo-spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}

86
src/app/app.js Normal file
View File

@ -0,0 +1,86 @@
import React, { Component } from 'react';
import './app.css';
import './router';
import PagesPie from './pages-pie.js';
//import registerServiceWorker from './registerServiceWorker';
import injectTapEventPlugin from 'react-tap-event-plugin';
import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider';
const { ipcRenderer, remote } = require('electron');
//var io = require("socket.io-client");
//window.torrentSocket = io(document.location.protocol + '//' + document.location.hostname + (process.env.NODE_ENV === 'production' ? '/' : ':8095/'));
window.torrentSocket = {}
window.torrentSocket.callbacks = {}
window.torrentSocket.on = (name, func) => {
ipcRenderer.on(name, (event, data) => {
func(data)
});
}
window.torrentSocket.off = (name, func) => {
if(!func)
ipcRenderer.removeListener(name);
else
ipcRenderer.removeListener(name, func);
}
window.torrentSocket.emit = (name, ...data) => {
if(typeof data[data.length - 1] === 'function')
{
const id = Math.random().toString(36).substring(5)
window.torrentSocket.callbacks[id] = data[data.length - 1];
data[data.length - 1] = {callback: id}
}
ipcRenderer.send(name, data)
}
ipcRenderer.on('callback', (event, id, data) => {
const callback = window.torrentSocket.callbacks[id]
if(callback)
callback(data)
delete window.torrentSocket.callbacks[id]
});
// Needed for onTouchTap
// http://stackoverflow.com/a/34015469/988941
injectTapEventPlugin();
//registerServiceWorker();
let loadersCount = 0;
let appReady = false;
window.customLoader = (func, onLoading, onLoaded) => {
loadersCount++;
if(onLoading) {
onLoading();
}
return (...args) => {
func(...args);
if(onLoaded) {
onLoaded();
}
loadersCount--;
}
};
window.isReady = () => {
return (appReady && loadersCount === 0)
}
class App extends Component {
componentDidMount() {
window.router()
appReady = true;
}
componentWillUnmount() {
appReady = false;
}
render() {
return (
<MuiThemeProvider>
<PagesPie />
</MuiThemeProvider>
);
}
}
export default App;

252
src/app/bad-words.js Normal file
View File

@ -0,0 +1,252 @@
let XXX_BLOCK_WORDS = ['incestcash', 'asacp', 'xondemand', 'yankscash', 'klixxx', 'cybersitter', 'safesurf',
'surfwatch', 'netcash', 'watersport', 'fuck', 'threesome', 'tits',
'masturbating', 'incest', 'bestiality', 'analintercourse', 'analsex', 'animesex',
'anitablonde', 'autosex', 'blackass', 'blackasses', 'boner', 'boobcruise',
'boobies', 'bordello', 'braless', 'brothel', 'callgirl', 'callgirls',
'clit', 'clitoris', 'clits', 'cums', 'cybererotic', 'cybererotica',
'cybersex', 'cybersexx', 'ejaculation', 'erotica', 'eroticfilm', 'eroticfilms',
'eroticism', 'eroticphoto', 'eroticphotography', 'eroticphotos', 'erotics', 'eroticsex',
'eroticsexstories', 'eroticstories', 'eroticstory', 'erotik', 'erotika', 'antasysex',
'gaysex', 'gruppensex', 'horny', 'jackoff', 'lesbo', 'lesbos',
'makinglove', 'motherfucker', 'horsesex', 'dogsex', 'snakesex', 'sextracker',
'tranny', 'bdsm', 'porn', 'beastality', 'bizarre', 'blowjob',
'bondage', 'bondages', 'celebs', 'cum', 'cunt', 'transsexual',
'transsexuals', 'cumbath', 'cumshot', 'dildo', 'dildos', 'transvestites',
'transvestite', 'dominatrix', 'spank', 'dungeon', 'sexbilder', 'dungeons',
'pussie', 'ebony', 'fisting', 'gayshop', 'facesitting', 'sexshop',
'pussy', 'bukkake', 'escort', 'blowjobs', 'facial', 'facialized',
'facials', 'fetish', 'footfetish', 'fuckingpussy', 'fucking', 'hardcore',
'hentai', 'hustlerlatex', 'latex', 'lolita', 'lolitas', 'mlm',
'oralsex', 'peepshow', 'peepshows', 'phonesex', 'pornstars', 'preteen',
'preteens', 'pussies', 'shemale', 'shemales', 'sluts', 'whores',
'whore', 'spanking', 'strapon', 'twinks', 'upskirts', 'voyeur',
'whip', 'whipping', 'xxx', 'sexkey', 'femdom', 'gangbang',
'gloryhole', 'adultbuffet', 'adultcartoons', 'adultchat', 'adultchatnetwork', 'adultdvd',
'adultentertainment', 'adultentertainmenthouse', 'adulterotica', 'adultfilms', 'adultfree', 'adultfreepics',
'adultfreepix', 'adultfun', 'adulthardcore', 'adultimages', 'adultjpeg', 'adultlink',
'adultlinks', 'adultmovie', 'adultmovies', 'adultmpeg', 'adultmpegs', 'adultpass',
'adultpersonals', 'adultphotos', 'adultpic', 'adultpics', 'adultpicture', 'adultpictures',
'adultpix', 'adultporn', 'adultpornography', 'adultsearch', 'adultsex', 'adultsights',
'adultsites', 'adultsonly', 'adultstars', 'adultstories', 'adultvideo', 'adultvideos',
'adultweb', 'adultxxx', 'alt.sex', 'altsex', 'alt.sex.exhibitionism', 'alt.sex.pictures',
'altsexst', 'alt.sex.stories', 'alt.sex.voyeurism', 'amateurerotica', 'amateurfotos', 'amateurhardcore',
'amateurindex', 'amateurnaked', 'amateurnudes', 'amateurporn', 'amateurpussy', 'amateursex',
'amateursxxx', 'amatuerhardcore', 'amatuersex', 'ampland', 'analaction', 'analcum',
'analfuck', 'analfucking', 'analgallery', 'analingus', 'analpics', 'analpicture',
'analsex', 'analsexpics', 'analsexpictures', 'analxxx', 'animalbeastiality', 'animehentai',
'animeporno', 'animexxx', 'asiacarrera', 'assfuck', 'assfucking', 'asshole',
'assholes', 'asslick', 'asspics', 'assworship', 'autofellatio', 'babepix',
'badgirls', 'bakedchicks', 'baldcunt', 'baldpussies', 'baldpussy', 'barecelebs',
'barenaked', 'barepussy', 'beastiality', 'beaverboy', 'beavershots', 'bigballs',
'bigblackboobs', 'bigblackcocks', 'bigblackdicks', 'bigblacktits', 'bigboob', 'bigboobs',
'bigbreast', 'bigbreastlovers', 'bigbreasts', 'bigbutt', 'bigbutts', 'bigclit',
'bigclitoris', 'bigclits', 'bigcock', 'bigcocks', 'bigcunt', 'bigcunts',
'bigdick', 'bigdicks', 'biggestboobs', 'biggestdick', 'biggesttit', 'biggesttits',
'bighairyballs', 'bighardons', 'bighooters', 'bignipples', 'bigpussies', 'bigpussy',
'bigtit', 'bigtits', 'bigtitties', 'bigtitts', 'bizarresex', 'bizarrexxx',
'bizzaresex', 'blackbondage', 'blackboobs', 'blackbooty', 'blackbutt', 'blackcock',
'blackcocks', 'blackcum', 'blackcunt', 'blackcunts', 'blackdick', 'blackdicks',
'blackerotica', 'blackhardcore', 'blacknudes', 'blackonblondes', 'blackporn', 'blackporno',
'blackpornography', 'blackpussy', 'blacksex', 'blacksluts', 'blacksonblondes', 'blacktit',
'blacktits', 'blacktwat', 'blackxxx', 'blondepussy', 'blondsgay', 'bondagefree',
'bondagegallery', 'bondagelinks', 'bondagepics', 'bondagepictures', 'bondagesex', 'bondagestories',
'bondagestory', 'bukake', 'bustybabes', 'bustyblondes', 'bustyceleb', 'butt-fuck',
'buttfuck', 'buttfucker', 'buttfuckers', 'buttfucking', 'buttholes', 'buttman',
'buttpics', 'buttplug', 'buttthumbnails', 'cartoonporn', 'cartoonsex', 'cartoonsxxx',
'cartoonxxx', 'celebritiesnaked', 'celebritiesnude', 'celebritiesxxx', 'celebritybush', 'celebritybutts',
'celebritynude', 'celebritynudes', 'celebrityporn', 'celebritypussy', 'celebritysex', 'celebrityxxx',
'celebsxxx', 'celebxxx', 'centerfolds', 'chaseylain', 'chatsex', 'cheerleadersxxx',
'cheerleaderxxx', 'chickswithdicks', 'christycanyon', 'cicciolina', 'clitpics', 'clitpictures',
'closeuppussy', 'comixxx', 'crotchless', 'cruisingforsex', 'cumbaths', 'cumcoveredcunts',
'cumcunt', 'cumdrinker', 'cumdrinkers', 'cumdrinking', 'cumdrops', 'cumeater',
'cumeaters', 'cumeating', 'cumface', 'cumfaces', 'cumfacial', 'cumfacials',
'cumgallery', 'cumgargle', 'cumguzzlers', 'cumincunt', 'cumjunkies', 'cumlovers',
'cummpeg', 'cumpic', 'cumpics', 'cumpicture', 'cumpictures', 'cumpussy',
'cumsluts', 'cumsucker', 'cumsuckers', 'cumsucking', 'cumswallow', 'cumswallowers',
'cumswallowing', 'cumtasting', 'cumthumbnails', 'cunilingus', 'cunnilingus', 'cuntcum',
'cuntfuck', 'cuntfucking', 'cuntjuice', 'cuntlapper', 'cuntlick', 'cuntlickers',
'cuntlicking', 'cuntpics', 'cuntpictures', 'cunts', 'cuntstories', 'cuntsuckers',
'cuntz', 'cyberlust', 'cybernude', 'cyberporn', 'cyberpornlinks', 'cyberpornsexlinks',
'cyberslut', 'damateur', 'danniashe', 'dicksex', 'dicksuckers', 'dicksucking',
'digixxx', 'directporn', 'dirtypictures', 'dirtysex', 'doggiestyle', 'doggystyle',
'domatrix', 'dominatrixes', 'downblouse', 'drinkingcum', 'drippingcunt', 'drippingcunts',
'drippingpussy', 'easypic.com', 'eatcum', 'eatingcum', 'eatingpussy', 'eatpussy',
'ebony+ayres', 'ebonyporn', 'ebonypussy', 'ebonysex', 'ebonyxxx', 'enormoustits',
'erosvillage', 'eroticanime', 'eroticart', 'eroticas', 'eroticastories', 'eroticfiction',
'eroticlesbianstories', 'eroticmovie', 'eroticmovies', 'erotico', 'eroticos', 'eroticpics',
'eroticpicture', 'eroticpictures', 'eroticsites', 'eroticsounds', 'erotictales', 'erotictext',
'eroticvideo', 'eroticvideos', 'eroticwomen', 'eroticwriting', 'erotikchat', 'erotique',
'erotismo', 'escortservice', 'escortservices', 'eurosex', 'explicitsex', 'facecum',
'facesit', 'facesitters', 'fastporn', 'fatass', 'fatsex', 'feetfetish',
'feetsex', 'felatio', 'fellatio', 'fellations', 'fetishwear', 'fettegirls',
'fingerbang', 'fingerfuck', 'flesh4free', 'footjobs', 'footlicking', 'footworship',
'fornication', 'freeanal', 'freeanalsex', 'freeass', 'freebigboobs', 'freebigtit',
'freebigtits', 'freeblackcunt', 'freeblackpussy', 'freeblowjob', 'freeblowjobs', 'freebondage',
'freeboobs', 'freecum', 'freecumshot', 'freecumshots', 'freecunt', 'freecunts',
'freedick', 'freeerotic', 'freeerotica', 'freeeroticstories', 'freefuck', 'freefucking',
'freefuckpics', 'freegay', 'freegaypics', 'freehardcore', 'freehardcorepics', 'freehardcorepictures',
'freehardcoreporn', 'freehardcoresex', 'freehardcoresexpics', 'freehentai', 'freehooters', 'freelargehooters',
'freelesbian', 'freelesbianporn', 'freelesbiansex', 'freenakedpic', 'freenakedpics', 'freenakedpictures',
'freenakedwomen', 'freenude', 'freenudecelebs', 'freenudephotos', 'freenudepics', 'freenudes',
'freeporn', 'freepornmovies', 'freeporno', 'freepornography', 'freepornopics', 'freepornopictures',
'freepornos', 'freepornpic', 'freepornpics', 'freepornpictures', 'freeporns', 'freepornsite',
'freepornsites', 'freepussy', 'freepussypic', 'freepussypics', 'freepussypictures', 'freesex',
'freesexchat', 'freesexmovies', 'freesexnet', 'freesexphotos', 'freesexpic', 'freesexpics',
'freesexpicture', 'freesexpictures', 'freesexsites', 'freesexstories', 'freesexvideos', 'freesexycam',
'freeshemale', 'freesmut', 'freetit', 'freetits', 'freevoyeur', 'freexxx',
'freexxxmovie', 'freexxxmovies', 'freexxxmpeg', 'freexxxphotos', 'freexxxpic', 'freexxxpics',
'freexxxpicture', 'freexxxpictures', 'freexxxstories', 'freexxxvideo', 'freexxxx', 'fuking',
'gangbangs', 'gratishardcoregalerie', 'hardcorecelebs', 'hardcorefisting', 'hardcorefree', 'hardcorefucking',
'hardcorehooters', 'hardcorejunkie', 'hardcorejunky', 'hardcoremovie', 'hardcoremovies', 'hardcorepic',
'hardcorepics', 'hardcorepictures', 'hardcorepix', 'hardcoreporn', 'hardcoreporno', 'hardcorepornography',
'hardcorepornos', 'hardcorepussy', 'hardcoresamples', 'hardcoresex', 'hardcoresexpictures', 'hardcorestories',
'hardcorethumbnails', 'hardcorevideo', 'hardcorevideos', 'hardcorexxx', 'harddicks', 'hardnipples',
'hardons', 'hardporn', 'indiasex', 'interacialhardcore', 'interacialsex', 'interacialxxx',
'intercoursepositions', 'internetsex', 'interracialfucking', 'interracialhardcore', 'interracialporn', 'interracialpornography',
'interracialsex', 'interracialsexstories', 'interracialxxx', 'intimatesex', 'ittybittytitty', 'japaneseporn',
'japanesesex', 'japansex', 'japanxxx', 'jennacam.com', 'jennajameson', 'jennicam',
'jerkoff', 'jism', 'jiz', 'jizz', 'juicycunts', 'juicypussy',
'justhardcore', 'karasamateurs', 'karasxxx', 'kascha', 'kaylakleevage', 'kobetai',
'koreasex', 'lapdance', 'largeclits', 'largecunts', 'largedicks', 'largehooters',
'largepussy', 'largetit', 'largetits', 'latinapussy', 'latinasex', 'latinosex',
'latinoxxx', 'latinxxx', 'legsex', 'lesbianerotica', 'lesbianhardcore', 'lesbianorgies',
'lesbianorgy', 'lesbianpics', 'lesbianpink', 'lesbianporn', 'lesbian-porno', 'lesbianporno',
'lesbianpornography', 'lesbianpornos', 'lesbianpussy', 'lesbiansex', 'lesbiansexpics', 'lesbiansexstories',
'lesbianxxx', 'lickadick', 'lickingpussy', 'lindalovelace', 'lingeriexxx', 'literotica',
'littleboobs', 'littlepussy', 'littletits', 'livefucking', 'liveporn', 'livesex',
'livesexcam', 'livesexcams', 'longdicks', 'lovedoll', 'lovedolls', 'magazinessex',
'makinglove', 'malaysex', 'malenudes', 'malesex', 'mangaporno', 'mangax',
'mangaxxx', 'manpics', 'mansex', 'marilynchambers', 'massivecocks', 'masterbating',
'masturbation', 'masturbationvideos', 'maturefucking', 'maturesex', 'maturexxx', 'megaboobs',
'megaporno', 'megapussy', 'megatits', 'mensdicks', 'mensex', 'm.i.l.f.',
'milf', 'milfhunterpic', 'milfmpegsample', 'modelsex', 'mondoporn', 'monstercocks',
'monsterdicks', 'motherfucker', 'moviepost.com', 'mpegsex', 'mpegxxx', 'muffdiving',
'myslutwife', 'nacktfotos', 'nakedbabes', 'nakedblackwomen', 'nakedcelebrity', 'nakedcelebs',
'nakedcheerleader', 'nakedchicks', 'nakedgirls', 'nakedguys', 'nakedladies', 'nakedlady',
'nakedman', 'nakedmen', 'nakedness', 'nakedphotographs', 'nakedphotography', 'nakedphotos',
'nakedpic', 'nakedpics', 'nakedpicture', 'nakedpictures', 'nakedpussy', 'nakedstars',
'nakedwife', 'nakedwoman', 'nakedwomen', 'nastychat', 'nastypussy', 'nastysex',
'nastythumbs', 'naturaltits', 'naughty.com', 'naughtylinks', 'naughtylinx', 'naughtylynx',
'naughtynurses', 'netsex', 'niceass', 'nicetits', 'nikkinova', 'nikkityler',
'nipples', 'nookie', 'nookies', 'nudeactress', 'nudeactresses', 'nudeamateur',
'nudeamateurs', 'nudeasianwomen', 'nudebabes', 'nudebigboobs', 'nudeblack', 'nudeblackwomen',
'nudeblondes', 'nudeceleb', 'nudeceleberties', 'nudecelebraties', 'nudecelebrites', 'nudecelebrities',
'nudecelebrity', 'nudecelebs', 'nudecollegegirls', 'nudefemale', 'nudefemales', 'nudefree',
'nudegay', 'nudeimages', 'nudeladies', 'nudelesbians', 'nudemale', 'nudemales',
'nudeman', 'nudemen', 'nudepic', 'nudepics', 'nudepicture', 'nudepictures',
'nudepornography', 'nudepussy', 'nuderaider', 'nuderedheads', 'nudes', 'nudesex',
'nudestar', 'nudestars', 'nudevideoconferencing', 'nudewoman', 'nudewomen', 'nudism',
'nudist', 'nudists', 'nudity', 'nylonfetish', 'nylonsex', 'nympho',
'nymphos', 'olderbabes', 'oldersex', 'oldersluts', 'oldpussy', 'oldsex',
'oldsluts', 'openlegs', 'openpussy', 'oral4free', 'oral-sex', 'oralsexpictures',
'orgie', 'orgies', 'orgypics', 'orgys', 'pantyhosefetish', 'pantyhosesex',
'peepcam', 'persiankitty', 'persiankitty.com', 'perverted', 'picsxxx', 'picturesex',
'picturesofsex', 'picturessex', 'pimpserver', 'pimpserver.com', 'pinkpussy', 'pissing',
'pixxx', 'poontang', 'pornagraphy', 'porncast', 'porncity', 'porndirectory',
'porne', 'pornfree', 'pornmovies', 'pornno', 'pornnude', 'porno',
'pornoadult', 'pornocartoons', 'pornochat', 'pornodeluxe', 'pornoe', 'pornofilms',
'pornofree', 'pornogame', 'pornografi', 'pornografia', 'pornografie', 'pornografy',
'pornograph', 'pornographi', 'pornographia', 'pornographic', 'pornographicpictures', 'pornographics',
'pornographicvideo', 'pornographicvideos', 'pornographie', 'pornography', 'pornographyfree', 'pornographyphotos',
'pornographypictures', 'pornography-sex', 'pornographysex', 'pornoland', 'pornolinks', 'pornolynx',
'pornomagazines', 'pornomovies', 'pornompeg', 'pornophotos', 'pornopic', 'pornopics',
'pornopicture', 'pornopictures', 'pornos', 'pornosex', 'pornosite', 'pornosites',
'pornostar', 'pornostories', 'pornovideo', 'pornovideos', 'pornoxxx', 'pornphotos',
'pornpics', 'pornpictures', 'pornpix', 'pornpost', 'pornqueens', 'pornrated',
'porns', 'pornsex', 'pornstar', 'pornstories', 'porntrack', 'pornvideo',
'pornvideos', 'pornypics', 'porstars', 'privatesex', 'privatex', 'privatexx',
'privatexxx', 'prono', 'pronography', 'publicnudity', 'puffynipples', 'purple+passion',
'pussyboard', 'pussycam', 'pussycams', 'pussycloseup', 'pussycloseups', 'pussycum',
'pussycunt', 'pussyeater', 'pussyeaters', 'pussyeating', 'pussyfuck', 'pussyfucking',
'pussyhair', 'pussyheaven', 'pussyjuice', 'pussylickers', 'pussylicking', 'pussylink',
'pussylips', 'pussyphotos', 'pussyporno', 'pussys', 'pussysex', 'pussyshots',
'pussythumbnails', 'pussytits', 'pusy', 'racqueldarrian', 'rape', 'rawlinks',
'rawpussy', 'rawsex', 'realhardcore', 'realsex', 'redneckporn', 'redpussy',
'rubberfetish', 'russianxxx', 'scat', 'schiffernude', 'seka', 'seniorsex',
'sex4free', 'sex66', 'sexacts', 'sexadult', 'sexaids', 'sexamateur',
'sexanal', 'sexandpictures', 'sexaphone', 'sexaudio', 'sexavi', 'sexbondage',
'sexboner', 'sexbuttfucker', 'sexcam', 'sexcams', 'sexchat', 'sexchatrooms',
'sexcites', 'sexclub', 'sexclubs', 'sex.com', 'sexdating', 'sexe',
'sexfantasies', 'sexfantasy', 'sexfilm', 'sexgallery', 'sexgame', 'sexgames',
'sexgirl', 'sexgirls', 'sexgroup', 'sexguide', 'sexhardcore', 'sexhphoto',
'sexhungry', 'sexhungryjoe', 'sexhungryjoes', 'sexi', 'seximages', 'sexis',
'sexjapan', 'sex.jpg', 'sexlinda', 'sexlinks', 'sexlinx', 'sexlive',
'sexmagazine', 'sexmagazines', 'sexmature', 'sexmelayu', 'sexmodels', 'sexmovie',
'sexmovies', 'sexmpeg', 'sexnude', 'sexnudity', 'sexo', 'sexoanal',
'sexole', 'sexontheinternet', 'sexoral', 'sexpasswords', 'sexphoto', 'sexphotos',
'sexpic', 'sexpicnet', 'sexpics', 'sexpicture', 'sexpictures', 'sexpicturesfree',
'sexpix', 'sexplaza', 'sexporn', 'sexporno', 'sexpositions', 'sexpussy',
'sexroulette', 'sexs', 'sexsamples', 'sexsearch', 'sexsex', 'sexsexsex',
'sexshare', 'sexshops', 'sexshow', 'sexshows', 'sexsite', 'sexsites',
'sexslave', 'sexsound', 'sexsounds', 'sexsource', 'sexspaces', 'sexspaces.com',
'sexstories', 'sexstory', 'sexstorys', 'sextalk', 'sexteen', 'sexthumbnails',
'sex-toons', 'sextoons', 'sextour', 'sextoy', 'sextoys', 'sextpus',
'sextracker.com', 'sextropolis', 'sexualbondage', 'sexualfantasies', 'sexualfantasy', 'sexualintercourse',
'sexualpictures', 'sexualpleasure', 'sexualpositions', 'sexuncensored', 'sexvideo', 'sexvision',
'sexvote', 'sexwomen', 'sexworld', 'sexx', 'sexxx', 'sexxxx',
'sexxxxx', 'sexyamateurs', 'sexybabes', 'sexyblack', 'sexyboobs', 'sexybookmark',
'sexybookmarka', 'sexybookmarks', 'sexybutts', 'sexycunts', 'sexygirl', 'sexygirls',
'sexyladies', 'sexylady', 'sexylegs', 'sexylingerie', 'sexymen', 'sexyphotos',
'sexypics', 'sexypictures', 'sexypost', 'sexypussies', 'sexypussy', 'sexysites',
'sexystories', 'sexywoman', 'sexywomen', 'shavedcunt', 'shavedcunts', 'shavedpussies',
'shavedpussy', 'shavedpussypics', 'shavedsluts', 'shavedwomen', 'shavenpussies', 'shavenpussy',
'shavepussy', 'sheboy', 'she-male', 'shemale+video', 'shemaleyum', 'showcam',
'showcams', 'showercam', 'showercams', 'slavesex', 'smallboobs', 'smallbreasts',
'smalldicks', 'smallpussy', 'smalltits', 'smut', 'smut69', 'smutland',
'smutserver', 'smutshack', 'softcore', 'softporn', 'softpornography', 'spankingpage',
'spreadpussy', 'spycamadult', 'stockingsex', 'storieserotic', 'storiessex', 'straightsex',
'stretchedcunt', 'stripclub', 'stripclubs', 'stripshow', 'striptease', 'strokeit',
'strokeme', 'suckdick', 'sucksex', 'superchicken', 'supersex', 'supertits',
'swallowcum', 'swinger', 'swingerclub', 'swingers', 'swollenclits', 'syberporn',
'sylviasaint', 'teenanal', 'teenanalsex', 'teenhardcore', 'teenie', 'teenies',
'teennude', 'teenpics', 'teenporn', 'teenporno', 'teenpussy', 'teensex',
'teenslut', 'teensluts', 'teensuck', 'teenxxx', 'tgp', 'thaipussy',
'thaisex', 'thehun', 'threesomes', 'thumblords', 'thumbnailssex', 'thumbzilla',
'tiffanytowers', 'tightcunt', 'tightpussies', 'tightpussy', 'tinypussy', 'tinytits',
'tinytitties', 'tisandass', 'tit', 'titbondage', 'titfuck', 'titfucking',
'tities', 'titman', 'titsandass', 'titsass', 'titties', 'titts',
'titty', 'tittyfuck', 'tittyfucking', 'tittys', 'tokyotopless', 'tommysbookmark',
'toplesswomen', 'trannies', 'transexual', 'transsexuels', 'twat', 'twats',
'twink', 'ultradonkey', 'ultrahardcore', 'uncutcocks', 'vaginalintercourse', 'vaginapictures',
'videoporno', 'videosex', 'videoxxx', 'vintageporn', 'virtualsex', 'vividtv',
'wank', 'wankers', 'wanking', 'wendywhoppers', 'wetcunt', 'wetcunts',
'wetdick', 'wetpanties', 'wetpussies', 'wetpussy', 'wetsex', 'wetvagina',
'whitepussy', 'whitesex', 'whitetits', 'whitexxx', 'wifesharing', 'wifeswapping',
'wildpussy', 'wildsex', 'womensex', 'worldsex', 'worldsexguide', 'x-rated',
'xrated', 'x-ratedmovie', 'x-ratedmovies', 'x-ratedvideo', 'x-ratedvideos', 'xxx4free',
'xxxadult', 'xxxadultmovies', 'xxxadultvideo', 'xxxadultvideos', 'xxxbabes', 'xxxcam',
'xxxcelebrities', 'xxxfree', 'xxxfreepics', 'xxxgalleries', 'xxxhardcore', 'xxxjapan',
'xxxlinks', 'xxxmodels', 'xxxmovie', 'xxxmovies', 'xxxmpeg', 'xxxpassword',
'xxxphoto', 'xxxphotos', 'xxxpic', 'xxx-pics', 'xxxpics', 'xxxpicture',
'xxxpictures', 'xxxpicturesfree', 'xxxpicx', 'xxxpirtures', 'xxxporn', 'xxxporno',
'xxxpussy', 'xxx-rated', 'xxxrated', 'xxxsex', 'xxxsites', 'xxxsluts',
'xxxstories', 'xxxteen', 'xxxthumbnails', 'xxxthumbs', 'xxxtoons', 'xxxtoys',
'xxxvideo', 'xxxvideos', 'xxxwomen', 'xxxx', 'xxxxx', 'xxxxxx',
'filthyfarm', 'adultcheck', 'erotic', 'mistresses', 'naughty',
'adult', 'amateur', 'amateurs', 'anal', 'analau', 'poker',
'anally', 'asian', 'swinging', 'ass', 'orgy', 'sexvideos',
'babe', 'babes', 'bare', 'glamour', 'casino', 'bed',
'vibrator', 'bikini', 'bisexual', 'blonde', 'breasted', 'breasts',
'busty', 'butts', 'candy', 'caning', 'caps', 'catholic',
'chained', 'chested', 'chubby', 'close-ups', 'coupling',
'cucumber', 'doll', 'dolls', 'dolly', 'dominating', 'drinking',
'drunk', 'fat', 'feet', 'fingering', 'fishnet', 'flasher',
'footjob', 'foreplay', 'foreplaying', 'girlfriend', 'girlfriends', 'gymnast',
'hairy', 'holly', 'horse', 'housewife', 'houswife', 'insertion',
'insertions', 'interracial', 'jerka', 'juicy', 'kissing', 'kitty',
'lactating', 'ladyboy', 'lance', 'latina', 'latins', 'leather',
'legs', 'lesbian', 'lesbians', 'licking', 'lingerie', 'lubricants',
'mature', 'midget', 'minor', 'mistress', 'nude',
'older', 'olez', 'oral', 'panties', 'pantyhose', 'pedro',
'peeing', 'penetration', 'pigtailed', 'pleasure', 'plump',
'plumper', 'poser', 'posers', 'poses', 'posing', 'prosecuted',
'punishment', 'reproducing', 'sex', 'shaved', 'shock', 'slave',
'sleeping', 'speculums', 'spread', 'spreading', 'squirting', 'stripper',
'stripping', 'swapping', 'thong', 'topless', 'toying',
'trix', 'undressing', 'uniform', 'whipcream', 'brazzers', 'порно',
'порн', 'лесб', 'гей', 'геи', 'прон', 'catgoddess', 'gracel', 'fatman', 'falko', 'pthc',
'ptsc', 'yukikax', 'ls-models', '3yo', '4yo', '5yo', '6yo', '7yo', '8yo', '9yo',
'10yo', '11yo', '12yo', '13yo', '14yo', '15yo', '16yo'
];
module.exports = XXX_BLOCK_WORDS;

33
src/app/component.js Normal file
View File

@ -0,0 +1,33 @@
import React, { Component } from 'react';
import { listenSwipe, removeSwipeListener } from './touch'
export default class BTComponent extends Component {
componentDidMount() {
// Свайп действия
if(
this.props.onSwipeLeft ||
this.props.onSwipeRight ||
this.props.onSwipeTop ||
this.props.onSwipeBottom ||
this.onSwipeLeft ||
this.onSwipeRight ||
this.onSwipeTop ||
this.onSwipeBottom
)
{
this.swipeFunctions = listenSwipe(this, {
left: this.props.onSwipeLeft || this.onSwipeLeft,
right: this.props.onSwipeRight || this.onSwipeRight,
top: this.props.onSwipeTop || this.onSwipeTop,
bottom: this.props.onSwipeBottom || this.onSwipeBottom,
initSwipe: this.props.initSwipe || this.initSwipe,
});
}
}
componentWillUnmount() {
if(this.swipeFunctions)
{
removeSwipeListener(this, this.swipeFunctions);
}
}
}

312
src/app/content.js Normal file
View File

@ -0,0 +1,312 @@
const ContentTypes = {
VIDEO: 'video',
AUDIO: 'audio',
PICTURES: 'pictures',
BOOKS: 'books',
APPLICATION: 'application',
ARCHIVE: 'archive',
DISC: 'disc',
}
const ExtesionBase = {
webm: ContentTypes.VIDEO,
mkv: ContentTypes.VIDEO,
flv: ContentTypes.VIDEO,
vob: ContentTypes.VIDEO,
ogv: ContentTypes.VIDEO,
drc: ContentTypes.VIDEO,
mng: ContentTypes.VIDEO,
avi: ContentTypes.VIDEO,
mov: ContentTypes.VIDEO,
qt: ContentTypes.VIDEO,
wmv: ContentTypes.VIDEO,
yuv: ContentTypes.VIDEO,
rm: ContentTypes.VIDEO,
rmvb: ContentTypes.VIDEO,
asf: ContentTypes.VIDEO,
amv: ContentTypes.VIDEO,
mp4: ContentTypes.VIDEO,
m4p: ContentTypes.VIDEO,
m4v: ContentTypes.VIDEO,
mpg: ContentTypes.VIDEO,
mpeg: ContentTypes.VIDEO,
mpv: ContentTypes.VIDEO,
svi: ContentTypes.VIDEO,
'3gp': ContentTypes.VIDEO,
'3g2': ContentTypes.VIDEO,
mxf: ContentTypes.VIDEO,
roq: ContentTypes.VIDEO,
nsv: ContentTypes.VIDEO,
f4v: ContentTypes.VIDEO,
ts: ContentTypes.VIDEO,
divx: ContentTypes.VIDEO,
m2ts: ContentTypes.VIDEO,
aa: ContentTypes.AUDIO,
aac: ContentTypes.AUDIO,
aax: ContentTypes.AUDIO,
act: ContentTypes.AUDIO,
aiff: ContentTypes.AUDIO,
amr: ContentTypes.AUDIO,
ape: ContentTypes.AUDIO,
au: ContentTypes.AUDIO,
awb: ContentTypes.AUDIO,
dct: ContentTypes.AUDIO,
dss: ContentTypes.AUDIO,
dvf: ContentTypes.AUDIO,
flac: ContentTypes.AUDIO,
gsm: ContentTypes.AUDIO,
iklax: ContentTypes.AUDIO,
ivs: ContentTypes.AUDIO,
m4a: ContentTypes.AUDIO,
mmf: ContentTypes.AUDIO,
mp3: ContentTypes.AUDIO,
mpc: ContentTypes.AUDIO,
msv: ContentTypes.AUDIO,
ogg: ContentTypes.AUDIO,
oga: ContentTypes.AUDIO,
opus: ContentTypes.AUDIO,
rm: ContentTypes.AUDIO,
ra: ContentTypes.AUDIO,
raw: ContentTypes.AUDIO,
sln: ContentTypes.AUDIO,
tta: ContentTypes.AUDIO,
vox: ContentTypes.AUDIO,
wav: ContentTypes.AUDIO,
wma: ContentTypes.AUDIO,
wv: ContentTypes.AUDIO,
ac3: ContentTypes.AUDIO,
jpg: ContentTypes.PICTURES,
jpeg: ContentTypes.PICTURES,
exif: ContentTypes.PICTURES,
gif: ContentTypes.PICTURES,
tiff: ContentTypes.PICTURES,
bmp: ContentTypes.PICTURES,
png: ContentTypes.PICTURES,
ppm: ContentTypes.PICTURES,
pgm: ContentTypes.PICTURES,
pbm: ContentTypes.PICTURES,
pnm: ContentTypes.PICTURES,
webp: ContentTypes.PICTURES,
heif: ContentTypes.PICTURES,
bpg: ContentTypes.PICTURES,
ico: ContentTypes.PICTURES,
tga: ContentTypes.PICTURES,
cd5: ContentTypes.PICTURES,
deep: ContentTypes.PICTURES,
ecw: ContentTypes.PICTURES,
fits: ContentTypes.PICTURES,
flif: ContentTypes.PICTURES,
ilbm: ContentTypes.PICTURES,
img: ContentTypes.PICTURES,
nrrd: ContentTypes.PICTURES,
pam: ContentTypes.PICTURES,
pcx: ContentTypes.PICTURES,
pgf: ContentTypes.PICTURES,
sgi: ContentTypes.PICTURES,
sid: ContentTypes.PICTURES,
vicar: ContentTypes.PICTURES,
psd: ContentTypes.PICTURES,
cpt: ContentTypes.PICTURES,
psp: ContentTypes.PICTURES,
xcf: ContentTypes.PICTURES,
svg: ContentTypes.PICTURES,
cgm: ContentTypes.PICTURES,
cdr: ContentTypes.PICTURES,
hvif: ContentTypes.PICTURES,
odg: ContentTypes.PICTURES,
vml: ContentTypes.PICTURES,
wmf: ContentTypes.PICTURES,
cbr: ContentTypes.BOOKS,
cbz: ContentTypes.BOOKS,
cb7: ContentTypes.BOOKS,
cbt: ContentTypes.BOOKS,
cba: ContentTypes.BOOKS,
lrf: ContentTypes.BOOKS,
lrx: ContentTypes.BOOKS,
chm: ContentTypes.BOOKS,
djvu: ContentTypes.BOOKS,
doc: ContentTypes.BOOKS,
docx: ContentTypes.BOOKS,
epub: ContentTypes.BOOKS,
pdf: ContentTypes.BOOKS,
pdb: ContentTypes.BOOKS,
fb2: ContentTypes.BOOKS,
xeb: ContentTypes.BOOKS,
ceb: ContentTypes.BOOKS,
htm: ContentTypes.BOOKS,
html: ContentTypes.BOOKS,
css: ContentTypes.BOOKS,
txt: ContentTypes.BOOKS,
ibooks: ContentTypes.BOOKS,
inf: ContentTypes.BOOKS,
azw3: ContentTypes.BOOKS,
azw: ContentTypes.BOOKS,
kf8: ContentTypes.BOOKS,
lit: ContentTypes.BOOKS,
prc: ContentTypes.BOOKS,
mobi: ContentTypes.BOOKS,
opf: ContentTypes.BOOKS,
txt: ContentTypes.BOOKS,
pdb: ContentTypes.BOOKS,
rtf: ContentTypes.BOOKS,
pdg: ContentTypes.BOOKS,
xml: ContentTypes.BOOKS,
tr2: ContentTypes.BOOKS,
tr3: ContentTypes.BOOKS,
oxps: ContentTypes.BOOKS,
xps: ContentTypes.BOOKS,
exe: ContentTypes.APPLICATION,
apk: ContentTypes.APPLICATION,
rpm: ContentTypes.APPLICATION,
deb: ContentTypes.APPLICATION,
jar: ContentTypes.APPLICATION,
bundle: ContentTypes.APPLICATION,
com: ContentTypes.APPLICATION,
so: ContentTypes.APPLICATION,
dll: ContentTypes.APPLICATION,
elf: ContentTypes.APPLICATION,
ipa: ContentTypes.APPLICATION,
xbe: ContentTypes.APPLICATION,
xap: ContentTypes.APPLICATION,
a: ContentTypes.APPLICATION,
bin: ContentTypes.APPLICATION,
msi: ContentTypes.APPLICATION,
dmg: ContentTypes.APPLICATION,
pbi: ContentTypes.APPLICATION,
tar: ContentTypes.ARCHIVE,
gz: ContentTypes.ARCHIVE,
bz2: ContentTypes.ARCHIVE,
rar: ContentTypes.ARCHIVE,
zip: ContentTypes.ARCHIVE,
lz: ContentTypes.ARCHIVE,
lzma: ContentTypes.ARCHIVE,
lzo: ContentTypes.ARCHIVE,
rz: ContentTypes.ARCHIVE,
sfark: ContentTypes.ARCHIVE,
sf2: ContentTypes.ARCHIVE,
xz: ContentTypes.ARCHIVE,
z: ContentTypes.ARCHIVE,
'7z': ContentTypes.ARCHIVE,
s7z: ContentTypes.ARCHIVE,
ace: ContentTypes.ARCHIVE,
afa: ContentTypes.ARCHIVE,
arc: ContentTypes.ARCHIVE,
ace: ContentTypes.ARCHIVE,
arj: ContentTypes.ARCHIVE,
b1: ContentTypes.ARCHIVE,
car: ContentTypes.ARCHIVE,
cfs: ContentTypes.ARCHIVE,
cpt: ContentTypes.ARCHIVE,
dar: ContentTypes.ARCHIVE,
ice: ContentTypes.ARCHIVE,
sfx: ContentTypes.ARCHIVE,
shk: ContentTypes.ARCHIVE,
sit: ContentTypes.ARCHIVE,
tgz: ContentTypes.ARCHIVE,
xar: ContentTypes.ARCHIVE,
zz: ContentTypes.ARCHIVE,
iso: ContentTypes.DISC,
mdf: ContentTypes.DISC,
mds: ContentTypes.DISC,
nrg: ContentTypes.DISC,
ima: ContentTypes.DISC,
imz: ContentTypes.DISC,
mdx: ContentTypes.DISC,
uif: ContentTypes.DISC,
isz: ContentTypes.DISC,
daa: ContentTypes.DISC,
cso: ContentTypes.DISC,
cue: ContentTypes.DISC,
fvd: ContentTypes.DISC,
ndif: ContentTypes.DISC,
udif: ContentTypes.DISC,
vdi: ContentTypes.DISC,
vhd: ContentTypes.DISC,
wim: ContentTypes.DISC,
};
const ContentTypeProp = 'contentType';
const ContentCategoryProp = 'contentCategory';
const XXX_BLOCK_WORDS = require('./bad-words');
// блокируем порнографию
const blockBadName = (torrent, name) => {
let splitName = name.split(/[`~!@#$%^&*()\]\[{}.,+?/\\;:\-_' "|]/);
splitName.some((word) => {
if (XXX_BLOCK_WORDS.some(function(v) { return word == v; })) {
torrent[ContentCategoryProp] = 'xxx';
}
return torrent[ContentCategoryProp] == 'xxx';
})
}
const detectSubCategory = (torrent, files, typesPriority, contentType) => {
let name = torrent.name.toLowerCase()
// блокируем порнографию
if(contentType == ContentTypes.VIDEO || contentType == ContentTypes.PICTURES || contentType == ContentTypes.ARCHIVE)
{
blockBadName(torrent, name);
// блокируем так по названию файлов
if(torrent[ContentCategoryProp] != 'xxx')
{
files.some(({path}) => {
let fileCheck = path.toLowerCase().split('.');
if(fileCheck.length > 1)
fileCheck.pop(); // убираем расширение на конце
fileCheck = fileCheck.join('.');
blockBadName(torrent, fileCheck);
if(torrent[ContentCategoryProp] == 'xxx')
{
console.log('block because file ' + path);
}
return torrent[ContentCategoryProp] == 'xxx';
})
}
}
}
const fileTypeDetect = (file) => {
let name = file.path.split('/').pop();
let extension = name.split('.').pop();
if(name.length == 0)
return;
if(extension.length == 0)
return;
extension = extension.toLowerCase();
return ExtesionBase[extension];
}
const torrentTypeDetect = (torrent, files) => {
let typesPriority = {};
for(let i = 0; i < files.length; i++) {
let file = files[i];
let type = fileTypeDetect(file)
if(type) {
if(!typesPriority[type])
typesPriority[type] = 0.;
typesPriority[type] += file.size / torrent.size;
}
}
let priority = Object.keys(typesPriority).sort(function(a, b){
return typesPriority[b] - typesPriority[a]
});
if(priority.length > 0)
torrent[ContentTypeProp] = priority[0];
detectSubCategory(torrent, files, typesPriority, torrent[ContentTypeProp]);
}
module.exports = {torrentTypeDetect, fileTypeDetect};

View File

@ -0,0 +1,256 @@
.animated {
transition: 0.2s;
}
.scale0 {
transform: scale(0,0);
}
.scale1 {
transform: scale(1,1);
}
/* - - - - - - - - - - - - - - -
АНИМАЦИИ
- - - - - - - - - - - - - - - - */
.an-zoom-in {
animation: zoom-in 0.2s;
}
@keyframes zoom-in {
0% {
opacity: 0;
transform: scale(0, 0);
}
100% {
opacity: 1;
transform: scale(1, 1);
}
}
.an-show-op {
animation: show-op 0.2s;
}
@keyframes show-op {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
.an-slide-r {
animation: slide-r 0.2s;
}
@keyframes slide-r {
0% {
opacity: 0;
transform: translateX(+50px);
}
100% {
opacity: 1;
transform: translateX(0px);
}
}
.an-slide-l {
animation: slide-l 0.2s;
}
@keyframes slide-l {
0% {
opacity: 0;
transform: translateX(-50px);
}
100% {
opacity: 1;
transform: translateX(0px);
}
}
.an-slide-t {
animation: slide-t 0.2s;
}
@keyframes slide-t {
0% {
opacity: 0;
transform: translateY(-50px);
}
100% {
opacity: 1;
transform: translateY(0px);
}
}
.an-slide-b {
animation: slide-b 0.2s;
}
@keyframes slide-b {
0% {
opacity: 0;
transform: translateY(50px);
}
100% {
opacity: 1;
transform: translateY(0px);
}
}
.an-flare-red {
animation: flare-red;
}
@keyframes flare-red {
0% {
box-shadow: 0px 0px 0px 10px rgba(255, 42, 42, 0.0);
}
30% {
box-shadow: 0px 0px 0px 25px rgba(255, 42, 42, 0.2);
}
100% {
box-shadow: 0px 0px 0px 50px rgba(255, 42, 42, 0.0);
}
}
.an-flare-green {
animation: flare-green;
}
@keyframes flare-green {
0% {
box-shadow: 0px 0px 0px 10px rgba(54, 215, 146, 0.0);
}
30% {
box-shadow: 0px 0px 0px 25px rgba(54, 215, 146, 0.2);
}
100% {
box-shadow: 0px 0px 0px 50px rgba(54, 215, 146, 0.0);
}
}
.an-flare-glam {
animation: flare-glam;
}
@keyframes flare-glam {
0% {
box-shadow: 0px 0px 0px 10px rgba(210, 95, 210, 0.0);
}
30% {
box-shadow: 0px 0px 0px 25px rgba(210, 95, 210, 0.25);
}
100% {
box-shadow: 0px 0px 0px 50px rgba(210, 95, 210, 0.0);
}
}
.an-flare-orange {
animation: flare-orange;
}
@keyframes flare-orange {
0% {
box-shadow: 0px 0px 0px 10px rgba(255, 173, 64, 0.0);
}
30% {
box-shadow: 0px 0px 0px 25px rgba(255, 173, 64, 0.25);
}
100% {
box-shadow: 0px 0px 0px 50px rgba(255, 173, 64, 0.0);
}
}
.an-flare-blue {
animation: flare-blue;
}
@keyframes flare-blue {
0% {
box-shadow: 0px 0px 0px 10px rgba(31,225,227, 0.0);
}
30% {
box-shadow: 0px 0px 0px 25px rgba(31,225,227, 0.25);
}
100% {
box-shadow: 0px 0px 0px 50px rgba(31,225,227, 0.0);
}
}
.an0-25 {
animation-duration: 0.25s;
}
.an0-5 {
animation-duration: 0.5s;
}
.an0-75 {
animation-duration: 0.75s;
}
.an1 {
animation-duration: 1s;
}
.an1-5 {
animation-duration: 1.5s;
}
.an2 {
animation-duration: 2s;
}
.an2-5 {
animation-duration: 2.5s;
}
.an-linear {
animation-timing-function: linear;
}
.an-infinite {
animation-iteration-count: infinite;
}
/* точечный спиннер */
.spinner {
text-align: center;
}
.spinner > div {
width: 0.5em;
height: 0.5em;
margin: 0.25em;
background-color: #dedede;
border-radius: 100%;
display: inline-block;
animation: sk-bouncedelay 1.4s infinite ease-in-out both;
}
.spinner .bounce1 {
animation-delay: -0.32s;
}
.spinner .bounce2 {
animation-delay: -0.16s;
}
@keyframes sk-bouncedelay {
0%, 80%, 100% {
transform: scale(0);
} 40% {
transform: scale(1.0);
}
}

View File

@ -0,0 +1,86 @@
.torrent-information-row .info-table {
flex-basis: 60%;
}
@media only screen and (max-width: 600px)
{
.torrent-information-row {
flex-direction: column;
}
.torrent-information-row .info-table {
flex-basis: 100%;
width: 100%;
}
}
@media only screen and (max-width: 445px)
{
.recent-title {
padding-left: 10px !important;
}
}
@media only screen and (max-width: 510px)
{
.donation-line {
width: 65%;
}
}
.recent-torrents {
width: 60%
}
@media only screen and (max-width: 1250px)
{
.recent-torrents {
width: 68%
}
}
@media only screen and (max-width: 1100px)
{
.recent-torrents {
width: 75%
}
}
@media only screen and (max-width: 800px)
{
.recent-torrents {
width: 90%
}
}
@media only screen and (max-width: 650px)
{
.recent-torrents {
width: 100%
}
}
.filter-control-border {
top: -10px;
padding-left: 40px;
}
@media only screen and (max-width: 530px)
{
.filter-control-row {
flex-wrap: wrap;
padding-top: 28px;
}
.filter-row {
padding-top: 30px;
align-items: inherit !important;
flex-direction: column;
}
.filter-control-border {
padding-top: 5px;
top: 6px;
padding-left: 0px;
}
}

178
src/app/css/izi/flex.css Normal file
View File

@ -0,0 +1,178 @@
/* FLEX */
.row, .column {
display: flex;
align-items: flex-start;
justify-content: flex-start;
}
.wrap {
flex-wrap: wrap;
}
.row.reverse {
flex-direction: row-reverse;
}
.column {
flex-direction: column;
}
.column.reverse {
flex-direction: column-reverse;
}
.row.center,
.column.inline {
justify-content: center;
}
.column.center,
.row.inline {
align-items: center;
}
.row.stretch,
.column.stretch {
align-items: stretch;
}
.row.top,
.column.left {
align-items: flex-start;
}
.row.bottom,
.colum.right {
align-items: flex-end;
}
.row > .self-top {
align-self: flex-end;
}
.row > .self-bottom {
align-self: flex-end;
}
.column > .self-right {
align-self: flex-end;
}
.column > .self-left {
align-self: flex-start;
}
.self-center {
align-self: center;
}
.self-stretch {
align-self: stretch;
}
.row.space-between,
.column.space-between {
justify-content: space-between;
}
.row.space-around,
.column.space-around {
justify-content: space-around;
}
.ml-auto {
margin-left: auto;
}
.mt-auto {
margin-top: auto;
}
.jc-end {
justify-content: flex-end;
}
.shrink0 {
flex-shrink: 0;
}
.shrink1 {
flex-shrink: 1;
}
.baseline {
align-items: baseline;
}
/* поток */
.column.indent0-25 > *:not(:first-child):not(.ripple) {
margin-top: 0.25em;
}
.column.indent0-5 > *:not(:first-child):not(.ripple) {
margin-top: 0.5em;
}
.column.indent0-75 > *:not(:first-child):not(.ripple) {
margin-top: 0.75em;
}
.column.indent1 > *:not(:first-child):not(.ripple) {
margin-top: 1em;
}
.column.indent1-25 > *:not(:first-child):not(.ripple) {
margin-top: 1.25em;
}
.column.indent1-5 > *:not(:first-child):not(.ripple) {
margin-top: 1.5em;
}
.column.indent1-75 > *:not(:first-child):not(.ripple) {
margin-top: 1.75em;
}
.column.indent2 > *:not(:first-child):not(.ripple) {
margin-top: 2em;
}
/* строка с остступами */
.row.indent0-25 > *:not(:first-child):not(.ripple) {
margin-left: 0.25em;
}
.row.indent0-5 > *:not(:first-child):not(.ripple) {
margin-left: 0.5em;
}
.row.indent0-75 > *:not(:first-child):not(.ripple) {
margin-left: 0.75em;
}
.row.indent1 > *:not(:first-child):not(.ripple) {
margin-left: 1em;
}
.row.indent1-25 > *:not(:first-child):not(.ripple) {
margin-left: 1.25em;
}
.row.indent1-5 > *:not(:first-child):not(.ripple) {
margin-left: 1.5em;
}
.row.indent1-75 > *:not(:first-child):not(.ripple) {
margin-left: 1.75em;
}
.row.indent2 > *:not(:first-child):not(.ripple) {
margin-left: 2em;
}
.row.indent.wrap {
flex-wrap: wrap;
}
.row.indent.wrap > * {
margin: 0 0.5em;
}
/* размеры */
.row > .full-size, .column > .full-size {
flex-basis: 100%;
}
.row.solid > *:not(.soft), .column.solid > *:not(.soft) {
flex-shrink: 0;
flex-grow: 0;
}

164
src/app/css/izi/inputs.css Normal file
View File

@ -0,0 +1,164 @@
/* переключалка */
[data-switch]:not(.butt):not(.show) {
display: none;
}
/* ---- инпуты ---- */
input[type="text"],
input[type="email"],
input[type="password"] {
font-size: inherit;
font-family: inherit;
width: 100%;
background: none;
outline: none;
border: none;
color: inherit;
}
select {
padding: 0;
font-family: inherit;
font-size: 1em;
background-color: transparent;
width: 100%;
border: none;
color: inherit;
}
select:focus {
outline: none;
}
textarea {
font-size: inherit;
font-family: inherit;
width: 100%;
resize: none;
background: none;
outline: none;
border: none;
}
/* обычный инпут */
.input-normal {
font-size: 1em;
padding: 0.75em;
display: flex;
}
.input-normal > *:not(:first-child) {
margin-left: 0.75em;
}
.input-normal svg {
height: 1em;
}
/* лэйбл-инпут */
.input-label {
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
align-items: center;
flex-direction: column;
justify-content: center;
font-size: 1em;
height: 4em;
cursor: text;
}
.input-label .combo {
position: relative;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
align-items: center;
}
.input-label .combo > * {
width: 100%;
text-align: center;
transition: 0.3s;
}
.input-label .placeholder {
position: absolute;
top: 0;
left: 0;
height: 100%;
}
.input-label.focused .placeholder {
transform: translateY(-100%);
}
.input-label input {
transform: scale(1, 0);
}
.input-label.focused input {
transform: scale(1, 1);
}
.input-label .underline {
width: 0;
opacity: 0;
border-bottom: 1px solid;
transform: translateY(0.5em);
}
.input-label.focused .underline {
width: 100%;
opacity: 1;
}
/* чекбокс-инпут */
label.input-checkbox {
cursor: pointer;
margin-left: auto;
}
label.input-checkbox .switcher {
position: relative;
width: 2em;
height: 1em;
border-radius: 1em;
padding: 0;
background: #d3d3d3;
-webkit-transition: .4s;
transition: .4s;
}
label.input-checkbox .switcher::before {
content: '';
position: absolute;
height: 1em;
width: 1em;
border-radius: 50%;
background: #fff;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24);
-webkit-transition: .4s;
transition: .4s;
}
.input-checkbox input[type="checkbox"]:checked ~ .switcher::before {
right: 0;
}
.input-checkbox input[type="checkbox"]:checked ~ .switcher {
background: #3F51B5;
}
.input-checkbox input[type="checkbox"] {
display: none;
}

480
src/app/css/izi/izi.css Normal file
View File

@ -0,0 +1,480 @@
html {
box-sizing: border-box;
}
*,
:before,
:after {
box-sizing: inherit;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
-webkit-touch-callout: none;
/* -webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
*/
}
body {
font-family: Roboto, Arial, Helvetica, sans-serif;
font-size: 16px;
margin: 0;
/* background: #ededed; */
cursor: default;
}
html,
body {
width: 100%;
}
svg {
height: 1em;
}
svg.iconmonstr {
padding: 0.10em;
}
.icon {
display: flex;
justify-content: center;
align-items: center;
}
/* фикс мерцания */
.flick-fix {
-webkit-backface-visibility: hidden;
}
/* ссылки */
a {
text-decoration: none;
color: inherit;
}
/* примитивные позиции */
.relative {
position: relative;
}
.absolute {
position: absolute;
}
.absolute.full-size {
top: 0;
left: 0;
bottom: 0;
right: 0;
}
/* указатель на нажатие */
.clickable {
cursor: pointer;
position: relative;
overflow: hidden;
z-index: 0;
transition: 0.5s;
-webkit-user-select: none; /* Chrome all / Safari all */
-moz-user-select: none; /* Firefox all */
-ms-user-select: none; /* IE 10+ */
user-select: none; /* Likely future */
}
.clickable .ripple {
display: block;
position: absolute;
border-radius: 100%;
transform:scale(0);
}
[class*="-bright-b"] .ripple {
background:rgba(80, 80, 80, 0.20);
}
[class*="-dark-b"] .ripple {
background:rgba(255, 255, 255, 0.4);
}
.an-ripple {
animation:ripple 0.65s linear;
}
@keyframes ripple {
100% {
opacity: 0; transform: scale(2.5);
}
}
/* разделитель */
.divider {
height: 1px;
width: 100%;
border-bottom: 1px solid;
}
[class*="-bright-b"] .divider:not([class*="-dark-c"]) {
color: rgba(0,0,0,0.07);
}
[class*="-dark-b"] .divider:not([class*="-bright-c"]) {
color: rgba(255,255,255,0.07);
}
.border1 {
border: 1px solid;
}
[class*="-bright-b"] .border1 {
border-color: rgba(0,0,0,0.10);
}
[class*="-dark-b"] .border1 {
border-color: rgba(255,255,255,0.10);
}
/* eщё разделитель */
[class*="-bright-b"] .underlined {
border-bottom: 1px solid rgba(0,0,0,0.07);
}
[class*="-dark-b"] .underlined {
border-bottom: 1px solid rgba(255,255,255,0.07);
}
[class*="-bright-b"] .upperlined {
border-top: 1px solid rgba(0,0,0,0.07);
}
[class*="-dark-b"] .upperlined {
border-top: 1px solid rgba(255,255,255,0.07);
}
/* круг */
.round {
border-radius: 50%;
}
/* закруглённые углы */
.rounded {
border-radius: 2px;
}
/* вот это повороты */
.rotate90 {
transform: rotate(90deg);
}
.rotate180 {
transform: rotate(180deg);
}
/* текст без переносов */
.text {
display: inline-block;
}
.text-nowrap {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
min-width: 0;
}
.text-bold {
font-weight: bold;
}
/* переносы по буквам */
.break-word {
word-break: break-word;
min-width:0;
}
/* upper-case */
.u-case {
text-transform: uppercase;
}
/* прокрутка */
.scroll {
overflow: auto;
}
.scrollY {
overflow-y: auto;
}
.scrollX {
overflow-x: auto;
}
.hidden {
visibility: hidden;
}
/* ---------------------
РАЗМЕРЫ
--------------------- */
.full-size {
width: 100%;
height: 100%;
}
.w100p {
width: 100%;
}
.h100p {
height: 100%;
}
.w50p {
width: 50%;
}
.h50p {
height: 50%;
}
.mw100p {
max-width: 100%;
}
.mh100p {
max-height: 100%;
}
.w1em {
width: 1em;
}
.h1em {
height: 1em;
}
.sz1em {
width: 1em;
height: 1em;
}
/* ---------------------
ПОЗИЦИИ
--------------------- */
.fixed {
position: fixed;
}
.top0 {
top: 0;
}
.left0 {
left: 0;
}
.right0 {
right: 0;
}
.bottom0 {
bottom: 0;
}
/* ---------------------
ТЕНИ
--------------------- */
.shadow1 {
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24);
}
.shadow2 {
box-shadow: 0 3px 6px rgba(0, 0, 0, 0.16), 0 3px 6px rgba(0, 0, 0, 0.23);
}
.shadow3 {
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.19), 0 6px 6px rgba(0, 0, 0, 0.23);
}
.shadow4 {
box-shadow: 0 14px 28px rgba(0, 0, 0, 0.25), 0 10px 10px rgba(0, 0, 0, 0.22);
}
.shadow5 {
box-shadow: 0 19px 38px rgba(0, 0, 0, 0.30), 0 15px 12px rgba(0, 0, 0, 0.22);
}
/* ---------------------
ПРОЗРАЧНОСТЬ
--------------------- */
.op75 {
opacity: 0.75;
}
.op5 {
opacity: 0.5;
}
.op25 {
opacity: 0.25;
}
/* ---------------------
МАРГИНЫ
--------------------- */
.mar0-25 {
margin: 0.25em;
}
/* ---------------------
ПАДДИНГИ
--------------------- */
.pad0-25 {
padding: 0.25em;
}
.pad0-5 {
padding: 0.5em;
}
.pad0-75 {
padding: 0.75em;
}
.pad1 {
padding: 1em;
}
.pad1-25 {
padding: 1.25em;
}
.pad1-5 {
padding: 1.5em;
}
/* ---------------------
РАЗМЕРЫ ШРИФТА
--------------------- */
.fs0-25 {
font-size: 0.25em;
}
.fs0-5 {
font-size: 0.5em;
}
.fs0-65 {
font-size: 0.65em;
}
.fs0-75 {
font-size: 0.75em;
}
.fs0-85 {
font-size: 0.85em;
}
.fs1 {
font-size: 1em;
}
.fs1-15 {
font-size: 1.15em;
}
.fs1-25 {
font-size: 1.25em;
}
.fs1-5 {
font-size: 1.5em;
}
.fs1-75 {
font-size: 1.75em;
}
.fs2 {
font-size: 2em;
}
.fs2-25 {
font-size: 2.25em;
}
.fs2-5 {
font-size: 2.5em;
}
.fs2-75 {
font-size: 2.75em;
}
.fs3 {
font-size: 3em;
}
.fs3-25 {
font-size: 3.25em;
}
.fs3-5 {
font-size: 3em;
}
.fs3-75 {
font-size: 3.75em;
}
/* ---------------------
Z-ИНДЕКС
--------------------- */
.z0 {
z-index: 0
}
.z1 {
z-index: 1
}
.z2 {
z-index: 2
}
.z3 {
z-index: 3
}
.z4 {
z-index: 4
}
.z5 {
z-index: 5
}
.overflow-hidden {
overflow: hidden;
}

View File

@ -0,0 +1,461 @@
/* - - - - - - - - - - - - -
ПАЛИТРА
- - - - - - - - - - - - - */
.veil-black-dark-c {
color: #303030;
fill: #303030;
}
.veil-black-dark-b {
background-color: #303030;
}
/* DARK BLUE */
.dark-blue-dark-c {
color: #151b27;
fill: #151b27;
}
.dark-blue-dark-b {
background-color: #151b27;
}
/* ЧЁРНЫЙ */
.black-dark-c {
color: rgba(0, 0, 0, 0.87);
fill: rgba(0, 0, 0, 0.87);
}
[class*="-bright-b"] {
color: rgba(0, 0, 0, 0.87);
fill:rgba(0, 0, 0, 0.54);
}
[class*="-bright-b"] .sec-c {
color: rgba(0, 0, 0, 0.54);
}
[class*="-bright-b"] .dis-c {
color: rgba(0, 0, 0, 0.38);
fill:rgba(0, 0, 0, 0.38);
}
[class*="-bright-b"] ::placeholder {
color: rgba(0, 0, 0, 0.38);
}
[class*="-bright-b"] .icon svg {
fill:rgba(0, 0, 0, 0.54);
}
[class*="-bright-b"] .dis-c .icon svg {
fill:rgba(0, 0, 0, 0.38);
}
.black-dark-b {
background-color: rgb(0, 0, 0);
}
/* БЕЛЫЙ */
.veil-white {
background: rgba(255,255,255,0.7);
}
.white-bright-c {
color: rgba(255,255,255,1);
fill: rgba(255,255,255,1);
}
[class*="-dark-b"] {
color: rgb(255,255,255);
fill: rgb(255, 255, 255);
}
[class*="-dark-b"] .sec-c {
color: rgba(255,255,255,0.70);
fill: rgba(255,255,255,0.70);
}
[class*="-dark-b"] .dis-c {
color: rgba(255,255,255,0.50);
fill: rgba(255,255,255,0.50);
}
[class*="-dark-b"] ::placeholder {
color: rgba(255,255,255,0.50);
}
[class*="-dark-b"] .icon svg {
fill: rgb(255,255,255);
}
[class*="-dark-b"] .dis-c .icon svg {
fill: rgba(255,255,255,0.50);
}
.white-bright-b {
background-color: rgb(255,255,255);
}
/* RED */
.red-dark-c {
color: #F44336;
fill: #F44336;
}
.red-dark-b {
background-color: #F44336;
}
.red-bright-c {
color: #FF8A80;
fill: #FF8A80;
}
.red-bright-b {
background-color: #FF8A80;
}
/* PINK */
.pink-dark-c {
color: #E91E63;
fill: #E91E63;
}
.pink-dark-b {
background-color: #E91E63;
}
.pink-bright-c {
color: #FF80AB;
fill: #FF80AB;
}
.pink-bright-b {
background-color: #FF80AB;
}
/* PURPLE */
.purple-dark-c {
color: #9C27B0;
fill: #9C27B0;
}
.purple-dark-b {
background-color: #9C27B0;
}
.purple-bright-c {
color: #EA80FC;
fill: #EA80FC;
}
.purple-bright-b {
background-color: #E040FB;
}
/* DEEP PURPLE */
.dpurple-dark-c {
color: #673AB7;
fill: #673AB7;
}
.dpurple-dark-b {
background-color: #673AB7;
}
.dpurple-bright-c {
color: #B388FF;
fill: #B388FF;
}
.dpurple-bright-b {
background-color: #B388FF;
}
/* INDIGO */
.indigo-dark-c {
color: #3F51B5;
fill: #3F51B5;
}
.indigo-dark-b {
background-color: #3F51B5;
}
.indigo-bright-c {
color: #8C9EFF;
fill: #8C9EFF;
}
.indigo-bright-b {
background-color: #8C9EFF;
}
/* BLUE */
.blue-dark-c {
color: #2196F3;
fill: #2196F3;
}
.blue-dark-b {
background-color: #2196F3;
}
.blue-bright-c {
color: #82B1FF;
fill: #82B1FF;
}
.blue-bright-b {
background-color: #82B1FF;
}
/* LIGHT BLUE */
.lblue-dark-c {
color: #039BE5;
fill: #039BE5;
}
.lblue-dark-b {
background-color: #039BE5;
}
.lblue-bright-c {
color: #40C4FF;
fill: #40C4FF;
}
.lblue-bright-b {
background-color: #40C4FF;
}
/* CYAN */
.cyan-dark-c {
color: #0097A7;
fill: #0097A7;
}
.cyan-dark-b {
background-color: #0097A7;
}
.cyan-bright-c {
color: #18FFFF;
fill: #18FFFF;
}
.cyan-bright-b {
background-color: #18FFFF;
}
/* TYAL */
.teal-dark-c {
color: #009688;
fill: #009688;
}
.teal-dark-b {
background-color: #009688;
}
.teal-bright-c {
color: #64FFDA;
fill: #64FFDA;
}
.teal-bright-b {
background-color: #64FFDA;
}
/* GREEN */
.green-dark-c {
color: #43A047;
fill: #43A047;
}
.green-dark-b {
background-color: #43A047;
}
.green-bright-c {
color: #69F0AE;
fill: #69F0AE;
}
.green-bright-b {
background-color: #69F0AE;
}
/* LIGHT GREEN */
.lgreen-dark-c {
color: #689F38;
fill: #689F38;
}
.lgreen-dark-b {
background-color: #689F38;
}
.lgreen-bright-c {
color: #B2FF59;
fill: #B2FF59;
}
.lgreen-bright-b {
background-color: #B2FF59;
}
/* LIME */
.lime-dark-c {
color: #827717;
fill: #827717;
}
.lime-dark-b {
background-color: #827717;
}
.lime-bright-c {
color: #EEFF41;
fill: #EEFF41;
}
.lime-bright-b {
background-color: #EEFF41;
}
/* YELLOW */
.yellow-bright-c {
color: #FFFF00;
fill: #FFFF00;
}
.yellow-bright-b {
background-color: #FFFF00;
}
/* AMBER */
.amber-bright-c {
color: #FFD740;
fill: #FFD740;
}
.amber-bright-b {
background-color: #FFD740;
}
/* ORANGE */
.orange-dark-c {
color: #EF6C00;
fill: #EF6C00;
}
.orange-dark-b {
background-color: #EF6C00;
}
.orange-bright-c {
color: #FFAB40;
fill: #FFAB40;
}
.orange-bright-b {
background-color: #FFAB40;
}
/* DEEP ORANGE */
.dorange-dark-c {
color: #FF5722;
fill: #FF5722;
}
.dorange-dark-b {
background-color: #FF5722;
}
.dorange-bright-c {
color: #FF6E40;
fill: #FF6E40;
}
.dorange-bright-b {
background-color: #FF6E40;
}
/* BROWN */
.brown-dark-c {
color: #795548;
fill: #795548;
}
.brown-dark-b {
background-color: #795548;
}
/* GREY */
.grey-dark-c {
color: #757575;
fill: #757575;
}
.grey-dark-b {
background-color: #757575;
}
.grey-bright-c {
color: #9E9E9E;
fill: #9E9E9E;
}
.grey-bright-b {
background-color: #9E9E9E;
}
/* BLUE GREY */
.bgrey-dark-c {
color: #607D8B;
fill: #607D8B;
}
.bgrey-dark-b {
background-color: #607D8B;
}

33
src/app/dmca-page.js Normal file
View File

@ -0,0 +1,33 @@
import React from 'react';
import Page from './page';
import AppBar from 'material-ui/AppBar';
import IconButton from 'material-ui/IconButton';
import NavigationClose from 'material-ui/svg-icons/navigation/close';
export default class DMCAPage extends Page {
render() {
return (
<div className='w100p column'>
<AppBar
title="DMCA / Copyright"
iconElementLeft={<IconButton onClick={()=>{ window.router('/') }}><NavigationClose /></IconButton>}
/>
<div className='column w100p pad1 center'>
<div>RatsOnTheBoat.org is in compliance with 17 U.S.C. § 512, the Digital Millennium Copyright Act ("DMCA") and the Directive 2001/29/EC of the European Parliament.</div>
<div className='fs1-5 pad0-75'>Content status</div>
Our main goal is collect and make analysis of information from the torrent network. We don't save/distribute any real content/data and also don't save any torrents files - we are respect DMCA law.
Information collected automaticly and the real source are torrent clients based on torrent protocol.
<div className='fs1-5 pad0-75'>Block mechanisms (for rightholders)</div>
Right holders can block/remove content that they responsible for. Contact administration or left application about content removal.
</div>
</div>
);
}
}

28
src/app/footer.js Normal file
View File

@ -0,0 +1,28 @@
import React from 'react';
export default (props) => {
return (
<div className='column center' style={{color: 'grey', marginTop: '12px'}}>
<div className='clickable pad0-75 fs0-75' onClick={() => {
window.router('/DMCA');
}}>
DMCA / Copyright
</div>
<div className='fs0-75 pad0-75 break-word donation-line' style={{color: 'grey'}}>Donation to support project (bitcoin): 1Ega5zeCSMPgyDn6fEMMiuGoqNNpS53ipK</div>
<svg style={{height: '100px', fill: 'grey'}} viewBox="0 0 264.725 264.725">
<path d="M220.195,71.427c-0.589-7.654-9.135-15.619-17.979-16.209c-8.844-0.584-17.398,0.301-12.087,6.483
c5.308,6.188,7.074,12.091,4.423,11.212c-2.66-0.896-13.267-7.08-45.104-2.066c-4.126,1.17-21.221-12.682-44.513-12.977
c-23.283-0.295-40.381,6.346-64.85,72.296c-2.356,5.828-18.866,19.386-27.71,25.865C3.536,162.529,0.007,169.787,0,182.763
c-0.018,18.158,25.934,27.187,81.648,26.889c55.715-0.292,85.195-9.388,85.195-9.388c-62.789,6.773-158.907,10.52-158.907-18.687
c0-20.641,28.321-28.47,36.281-28.184c7.958,0.3,13.562,12.673,33.307,5.603c3.247-0.295,1.48,4.423-1.18,7.369
c-2.651,2.942-0.586,6.487,9.73,6.487c10.315,0,41.183,0.295,47.707,0c6.531-0.299,11.839-11.792-9.384-12.68
c-18.548,0.311,12.023-5.773,15.915-21.813c0.709-3.927,8.84-4.139,15.918-4.119c20.777,0.029,34.485,38.193,38.912,38.338
c4.416,0.15,17.979,1.621,17.683-4.273c-0.292-5.897-11.491-3.241-13.854-6.487c-2.359-3.234-10.023-15.504-7.366-21.104
c2.65-5.59,12.674-21.229,24.463-22.988c11.789-1.777,42.451,7.361,47.459,0c5.012-7.372-6.783-11.512-15.918-28.611
C243.779,80.572,238.768,71.728,220.195,71.427z"/>
</svg>
<div className='fs0-75 pad0-75'>Don't hesitate and visit the banners, we are trying to survive among dark blue sea</div>
</div>
)
}

8
src/app/format-bytes.js Normal file
View File

@ -0,0 +1,8 @@
export default function formatBytes(bytes,decimals) {
if(bytes == 0) return '0 Byte';
var k = 1000; // or 1024 for binary
var dm = decimals + 1 || 3;
var sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
var i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 218 KiB

47
src/app/index-page.js Normal file
View File

@ -0,0 +1,47 @@
import React from 'react';
import Page from './page';
import Footer from './footer';
import RecentTorrents from './recent-torrents'
import Search from './search'
import {Card, CardActions, CardHeader, CardMedia, CardTitle, CardText} from 'material-ui/Card';
import Background from './images/pirate-mod.jpg'
const Header = (props) => {
return (
<Card>
<CardMedia
overlay={<CardTitle title="Yarrr, Landlubbers!" subtitle="Welcome to torrent project" />}
>
<img src={Background} />
</CardMedia>
<CardText>
Welcome to BT Search! This is file search engine based on the torrents from the internet.
Here you can easily find torrent or file that you intrested for. We are not responsible for any content of the site:
this is only information about content that collected automatically! Content right holders and users can mark/block bad content.
</CardText>
</Card>
)
}
export {Header}
export default class IndexPage extends Page {
constructor(props) {
super(props)
this.setTitle('Rats On The Boat - Content Search Engine');
}
render() {
return (
<div id='index-window'>
<Header />
<Search />
<div className='column center w100p pad0-75'>
<RecentTorrents />
<Footer />
</div>
</div>
);
}
}

5
src/app/index.css Normal file
View File

@ -0,0 +1,5 @@
body {
margin: 0;
padding: 0;
font-family: sans-serif;
}

17
src/app/index.js Normal file
View File

@ -0,0 +1,17 @@
import React from 'react';
import ReactDOM from 'react-dom';
import App from './app';
import './css/izi/izi.css';
import './css/izi/material-palette.css';
import './css/izi/flex.css';
import './css/izi/inputs.css';
import './css/izi/components.css';
import './css/izi/animations.css';
import './index.css';
ReactDOM.render(
<App />,
document.getElementById('mount-point')
);

View File

@ -0,0 +1,72 @@
import React, { Component } from 'react';
import InputRange from 'react-input-range';
import './input-range.css';
import formatBytes from './format-bytes'
import SelectField from 'material-ui/SelectField';
import MenuItem from 'material-ui/MenuItem';
import Checkbox from 'material-ui/Checkbox';
export default class InputFilesFilter extends Component {
constructor(props)
{
super(props)
this.state = {
files: this.props.value || { min: 0, max: 50 },
enabled: false || this.props.enabled,
filesMax: this.props.filesMax || 100 // 1mb
}
}
setState(val)
{
if(val.filesMax && this.state.files.max > val.filesMax)
val.files = {min: this.state.files.min, max: val.filesMax};
if(val.filesMax && this.state.files.min > val.filesMax)
val.files = {min: 0, max: val.files ? val.files.max || this.state.files.max : this.state.files.max };
super.setState(val, () => {
if(this.props.onChange)
this.props.onChange({
enabled: this.state.enabled,
filesMax: this.state.filesMax,
files: !this.state.enabled ? {min: 0, max: 0} : this.state.files
})
})
}
render() {
return (
<div className='filter-row row inline w100p'>
<Checkbox
label="Files filter"
checked={this.state.enabled}
style={{width: 150, display: 'flex', minWidth: 130}}
onCheck={() => this.setState({enabled: !this.state.enabled})}
/>
<div className='filter-control-row row inline w100p' style={{opacity: this.state.enabled ? 1 : 0.4, transition: '0.5s', paddingLeft: 9}}>
<InputRange
maxValue={this.state.filesMax}
minValue={0}
value={this.state.files}
style={this.props.style}
className={this.props.className}
onChange={files => this.setState({ files })}
/>
<SelectField
floatingLabelText="Size type"
value={this.state.filesMax}
onChange={(event, index, value) => this.setState({filesMax: value})}
className='filter-control-border'
>
<MenuItem value={10} primaryText="10 Files or less" />
<MenuItem value={100} primaryText="100 Files or less" />
<MenuItem value={1000} primaryText="1000 Files or less" />
<MenuItem value={10000} primaryText="10000 Files or less" />
<MenuItem value={100000} primaryText="100000 Files or less" />
<MenuItem value={1000000} primaryText="1000000 Files or less" />
</SelectField>
</div>
</div>
);
}
}

83
src/app/input-range.css Normal file
View File

@ -0,0 +1,83 @@
.input-range__slider {
appearance: none;
background: #3f51b5;
border: 1px solid #3f51b5;
border-radius: 100%;
cursor: pointer;
display: block;
height: 1rem;
margin-left: -0.5rem;
margin-top: -0.65rem;
outline: none;
position: absolute;
top: 50%;
transition: transform 0.3s ease-out, box-shadow 0.3s ease-out;
width: 1rem; }
.input-range__slider:active {
transform: scale(1.3); }
.input-range__slider:focus {
box-shadow: 0 0 0 5px rgba(63, 81, 181, 0.2); }
.input-range--disabled .input-range__slider {
background: #cccccc;
border: 1px solid #cccccc;
box-shadow: none;
transform: none; }
.input-range__slider-container {
transition: left 0.3s ease-out; }
.input-range__label {
color: #aaaaaa;
font-family: "Helvetica Neue", san-serif;
font-size: 0.8rem;
transform: translateZ(0);
white-space: nowrap; }
.input-range__label--min,
.input-range__label--max {
bottom: -1.4rem;
position: absolute; }
.input-range__label--min {
left: 0; }
.input-range__label--max {
right: 0; }
.input-range__label--value {
position: absolute;
top: -1.8rem; }
.input-range__label-container {
left: -50%;
position: relative; }
.input-range__label--max .input-range__label-container {
left: 50%; }
.input-range__track {
background: #eeeeee;
border-radius: 0.3rem;
cursor: pointer;
display: block;
height: 0.3rem;
position: relative;
transition: left 0.3s ease-out, width 0.3s ease-out; }
.input-range--disabled .input-range__track {
background: #eeeeee; }
.input-range__track--background {
left: 0;
margin-top: -0.15rem;
position: absolute;
right: 0;
top: 50%; }
.input-range__track--active {
background: #3f51b5; }
.input-range {
height: 1rem;
position: relative;
width: 100%; }
/*# sourceMappingURL=input-range.css.map */

73
src/app/input-size.js Normal file
View File

@ -0,0 +1,73 @@
import React, { Component } from 'react';
import InputRange from 'react-input-range';
import './input-range.css';
import formatBytes from './format-bytes'
import SelectField from 'material-ui/SelectField';
import MenuItem from 'material-ui/MenuItem';
import Checkbox from 'material-ui/Checkbox';
export default class InputSize extends Component {
constructor(props)
{
super(props)
this.state = {
size: this.props.value || { min: 0, max: 500 * 1024 },
enabled: false || this.props.enabled,
maxSize: this.props.maxSize || 1024 * 1024 // 1mb
}
}
setState(val)
{
if(val.maxSize && this.state.size.max > val.maxSize)
val.size = {min: this.state.size.min, max: val.maxSize};
if(val.maxSize && this.state.size.min > val.maxSize)
val.size = {min: 0, max: val.size ? val.size.max || this.state.size.max : this.state.size.max };
super.setState(val, () => {
if(this.props.onChange)
this.props.onChange({
enabled: this.state.enabled,
maxSize: this.state.maxSize,
size: !this.state.enabled ? {min: 0, max: 0} : this.state.size
})
})
}
render() {
return (
<div className='filter-row row inline w100p'>
<Checkbox
label="Size filter"
checked={this.state.enabled}
style={{width: 150, display: 'flex', minWidth: 130}}
onCheck={() => this.setState({enabled: !this.state.enabled})}
/>
<div className='filter-control-row row inline w100p' style={{opacity: this.state.enabled ? 1 : 0.4, transition: '0.5s', paddingLeft: 9}}>
<InputRange
maxValue={this.state.maxSize}
minValue={0}
value={this.state.size}
formatLabel={size => formatBytes(size)}
style={this.props.style}
className={this.props.className}
onChange={size => this.setState({ size })}
/>
<SelectField
floatingLabelText="Size type"
value={this.state.maxSize}
onChange={(event, index, value) => this.setState({maxSize: value})}
className='filter-control-border'
>
<MenuItem value={1024} primaryText="KB" />
<MenuItem value={1024 * 1024} primaryText="MB" />
<MenuItem value={1024 * 1024 * 1024} primaryText="GB" />
<MenuItem value={10 * 1024 * 1024 * 1024} primaryText="10 GB" />
<MenuItem value={100 * 1024 * 1024 * 1024} primaryText="100 GB" />
<MenuItem value={1024 * 1024 * 1024 * 1024} primaryText="TB" />
</SelectField>
</div>
</div>
);
}
}

40
src/app/page.js Normal file
View File

@ -0,0 +1,40 @@
import React from 'react';
import Component from './component'
export default class Page extends Component {
setTitle(title) {
if(title) {
document.title = title;
}
}
setDescription(description) {
this.setMetaTag('description', description);
}
findMetaTag(name) {
const head = document.getElementsByTagName('head')[0];
const headChilds = head.children;
let meta;
for(let i = 0; i < headChilds.length; i++) {
if(headChilds[i].nodeName.toLowerCase() == 'meta' && headChilds[i].name.toLowerCase() == name) {
meta = headChilds[i];
break;
}
}
return {head, meta};
}
setMetaTag(name, content) {
let {head, meta} = this.findMetaTag(name);
if(!meta) {
meta = document.createElement('meta');
head.appendChild(meta);
}
meta.name = name;
meta.content = content;
}
removeMetaTag(name) {
let {head, meta} = this.findMetaTag(name);
if(meta) {
head.removeChild(meta);
}
}
}

89
src/app/pages-pie.js Normal file
View File

@ -0,0 +1,89 @@
import React, { Component } from 'react';
export default class PagesPie extends Component {
constructor(props) {
super(props);
// синглтон
if ( PagesPie.instance ) {
return PagesPie.instance;
}
PagesPie.instance = this;
this.pie = [];
}
open(pages, params) {
if (params && params.replace) {
if (params.replace === 'all') {
this.pie = [];
} else
if (params.replace === 'last') {
this.pie.pop();
}
this.forceUpdate();
delete params.replace;
}
setTimeout(() => {
if (Array.isArray(pages)) {
for (let i in pages) {
this.pie.push({
Page: pages[i],
params: params
});
}
} else {
this.pie.push({
Page: pages,
params: params
});
}
this.forceUpdate();
}, 0);
}
close(count) {
if (count && typeof count === 'number') {
for (let i = 0; i < count; i++) {
this.pie.pop();
}
} else {
this.pie.pop();
}
this.forceUpdate();
}
findOpened(windowType) {
for (let i in this.refs) {
if(this.refs[i] instanceof windowType)
return this.refs[i];
}
}
// ОТРИСОВКА
render() {
if (this.pie.length > 0) {
return (
<div
className={'pie full-size ' + (this.props.className || '')}
>
{
this.pie.map(({Page, params}, index) => {
let focus = false;
if (index === this.pie.length-1) {
focus = true;
}
return (
<Page
focused={focus}
closeHandler={() => { index> 0 ? this.close() : null}}
index={index}
key={index}
ref={index}
{...params}
>
</Page>
)
})
}
</div>
)
} else {
return null
}
}
}

17
src/app/rating.js Normal file
View File

@ -0,0 +1,17 @@
function biasValuation(count, min, avrg, avrg_const)
{
return ((count / (count + min)) * avrg) + ((min / (min + count)) * avrg_const);
}
function rating(good, bad){
if (good + bad > 0)
{
return biasValuation(good + bad, 9, good / (good + bad), 0.45);
}
else
{
return 0;
}
}
module.exports = rating;

159
src/app/recent-torrents.js Normal file
View File

@ -0,0 +1,159 @@
import React, { Component } from 'react';
import TorrentLine from './torrent'
import {List} from 'material-ui/List';
import Divider from 'material-ui/Divider';
import Subheader from 'material-ui/Subheader';
import Paper from 'material-ui/Paper';
import FlatButton from 'material-ui/FlatButton';
import RefreshIndicator from 'material-ui/RefreshIndicator';
export default class RecentTorrents extends Component {
constructor() {
super()
this.torrents = [];
this.torrentsAssoc = {};
this.displayQueue = [];
this.displayQueueAssoc = {};
this.maxQueueSize = 1000;
this.maxDisplaySize = 10;
this.state = {
pause: false,
searchingIndicator: false
}
}
componentDidMount() {
window.torrentSocket.emit('recentTorrents', window.customLoader((data) => {
if(data) {
this.torrents = data;
//this.forceUpdate(); // вызывается через searchingIndicator
}
this.displayNewTorrent = () => {
if(!this.displayNewTorrent) {
return;
}
if(this.displayQueue.length == 0) {
setTimeout(this.displayNewTorrent, 1000);
return;
}
const speed = 850;
if(this.state.pause) {
setTimeout(this.displayNewTorrent, speed);
return;
}
let torrent = this.displayQueue.shift();
this.torrents.unshift(torrent);
this.torrentsAssoc[torrent.hash] = torrent;
if(this.torrents.length > this.maxDisplaySize) {
let toDelete = this.torrents.pop()
delete this.torrentsAssoc[toDelete.hash];
delete this.displayQueueAssoc[toDelete.hash];
}
this.displayTorrentCounterValue = this.displayQueue.length;
this.forceUpdate();
setTimeout(this.displayNewTorrent, speed);
}
this.displayNewTorrent();
this.displayTorrentCounterValue = 0;
this.displayTorrentCounter = setInterval(() => {
if(this.displayTorrentCounterValue != this.displayQueue.length) {
this.displayTorrentCounterValue = this.displayQueue.length;
this.forceUpdate();
}
}, 40);
}, () => {
this.setState({
searchingIndicator: true
});
}, () => {
this.setState({
searchingIndicator: false
});
}));
this.newTorrentFunc = (torrent) => {
if(this.displayQueue.length < this.maxQueueSize) {
this.displayQueue.push(torrent);
this.displayQueueAssoc[torrent.hash] = torrent;
}
};
window.torrentSocket.on('newTorrent', this.newTorrentFunc);
this.tracketUpdate = (statistic) => {
if(statistic.hash in this.displayQueueAssoc)
{
Object.assign(this.displayQueueAssoc[statistic.hash], statistic);
if(statistic.hash in this.torrentsAssoc) {
this.forceUpdate();
}
}
}
window.torrentSocket.on('trackerTorrentUpdate', this.tracketUpdate);
}
pauseAndContinue() {
this.setState({
pause: !this.state.pause
});
}
componentWillUnmount() {
if(this.newTorrentFunc)
window.torrentSocket.off('newTorrent', this.newTorrentFunc);
if(this.tracketUpdate)
window.torrentSocket.off('trackerTorrentUpdate', this.tracketUpdate);
if(this.displayNewTorrent)
delete this.displayNewTorrent;
if(this.displayTorrentCounter)
clearInterval(this.displayTorrentCounter);
}
render() {
const style = {
refresh: {
display: 'inline-block',
position: 'relative',
},
};
if(this.state.searchingIndicator) {
return (
<div className='pad1'>
<RefreshIndicator
size={50}
left={0}
top={0}
loadingColor="#FF9800"
status="loading"
style={style.refresh}
/>
</div>
);
}
if(!this.torrents || this.torrents.length == 0)
return null;
return (
<List className='animated recent-torrents'>
<Subheader className='recent-title' inset={true}>
<FlatButton style={{marginRight: '8px'}} primary={true} label='top' labelStyle={{color: "#a4c639"}} onClick={() =>{
window.router('/top');
}} />
<FlatButton style={{marginRight: '8px'}} label={!this.state.pause ? 'running' : 'stoped'} secondary={this.state.pause} primary={!this.state.pause} onClick={() =>{
this.pauseAndContinue()
}} />
Most recent torrents{this.displayQueue.length > 0 ? ` (and ${this.displayQueue.length} more)` : null}
</Subheader>
<Divider />
{
this.torrents.map((torrent, index) =>{
return <TorrentLine key={index} torrent={torrent} />;
})
}
</List>
);
}
}

View File

@ -0,0 +1,113 @@
// In production, we register a service worker to serve assets from local cache.
// This lets the app load faster on subsequent visits in production, and gives
// it offline capabilities. However, it also means that developers (and users)
// will only see deployed updates on the "N+1" visit to a page, since previously
// cached resources are updated in the background.
// To learn more about the benefits of this model, read https://goo.gl/KwvDNy.
// This link also includes instructions on opting out of this behavior.
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}$/
)
);
export default function register() {
if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
// The URL constructor is available in all browsers that support SW.
const publicUrl = new URL(process.env.PUBLIC_URL, window.location);
if (publicUrl.origin !== window.location.origin) {
// Our service worker won't work if PUBLIC_URL is on a different origin
// from what our page is served on. This might happen if a CDN is used to
// serve assets; see https://github.com/facebookincubator/create-react-app/issues/2374
return;
}
window.addEventListener('load', () => {
const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
if (!isLocalhost) {
// Is not local host. Just register service worker
registerValidSW(swUrl);
} else {
// This is running on localhost. Lets check if a service worker still exists or not.
checkValidServiceWorker(swUrl);
}
});
}
}
function registerValidSW(swUrl) {
navigator.serviceWorker
.register(swUrl)
.then(registration => {
registration.onupdatefound = () => {
const installingWorker = registration.installing;
installingWorker.onstatechange = () => {
if (installingWorker.state === 'installed') {
if (navigator.serviceWorker.controller) {
// At this point, the old content will have been purged and
// the fresh content will have been added to the cache.
// It's the perfect time to display a "New content is
// available; please refresh." message in your web app.
console.log('New content is available; please refresh.');
} else {
// At this point, everything has been precached.
// It's the perfect time to display a
// "Content is cached for offline use." message.
console.log('Content is cached for offline use.');
}
}
};
};
})
.catch(error => {
console.error('Error during service worker registration:', error);
});
}
function checkValidServiceWorker(swUrl) {
// Check if the service worker can be found. If it can't reload the page.
fetch(swUrl)
.then(response => {
// Ensure service worker exists, and that we really are getting a JS file.
if (
response.status === 404 ||
response.headers.get('content-type').indexOf('javascript') === -1
) {
// No service worker found. Probably a different app. Reload the page.
navigator.serviceWorker.ready.then(registration => {
registration.unregister().then(() => {
window.location.reload();
});
});
} else {
// Service worker found. Proceed as normal.
registerValidSW(swUrl);
}
})
.catch(() => {
console.log(
'No internet connection found. App is running in offline mode.'
);
});
}
export function unregister() {
if ('serviceWorker' in navigator) {
return navigator.serviceWorker.ready.then(registration => {
console.log("Unregister service worker");
return registration.unregister();
});
} else {
return new Promise(function(resolve, reject) {
resolve();
})
}
}

84
src/app/router.js Normal file
View File

@ -0,0 +1,84 @@
//import router from 'page';
import PagesPie from './pages-pie.js';
import IndexPage from './index-page.js'
import TorrentPage from './torrent-page.js'
import DMCAPage from './dmca-page.js'
import AdminPage from './admin-page.js'
import TopPage from './top-page.js'
let routers = {}
const router = (page, callback) => {
if(!callback)
{
if(!page)
routers['/'].callback()
else
{
const p = page.split('/')
const pg = routers[`${p[0]}/${p[1]}`]
if(!pg)
return
p.splice(0, 2)
const params = {}
for(let i = 0; i < p.length; i++)
{
params[pg.args[i]] = p[i]
}
console.log(params)
pg.callback({
params
})
}
return;
}
const p = page.split('/')
routers[`${p[0]}/${p[1]}`] = {callback}
routers[`${p[0]}/${p[1]}`].args = []
for(let i = 2; i < p.length; i++)
{
if(p[i].startsWith(':'))
routers[`${p[0]}/${p[1]}`].args.push(p[i].substring(1))
}
}
window.router = router;
router('/', () => {
//singleton
let pie = new PagesPie;
pie.open(IndexPage, {replace: 'all'});
});
router('/torrent/:hash', (e) => {
//singleton
let pie = new PagesPie;
pie.open(TorrentPage, {
replace: 'all',
hash: e.params.hash,
});
});
router('/DMCA', () => {
//singleton
let pie = new PagesPie;
pie.open(DMCAPage, {replace: 'all'});
});
router('/admi5p', () => {
//singleton
let pie = new PagesPie;
pie.open(AdminPage, {replace: 'all'});
});
router('/top', () => {
//singleton
let pie = new PagesPie;
pie.open(TopPage, {replace: 'all'});
});

View File

@ -0,0 +1,56 @@
import React, { Component } from 'react';
import SelectField from 'material-ui/SelectField';
import MenuItem from 'material-ui/MenuItem';
import InputSize from './input-size';
import FilesFilterInput from './input-files-filter';
export default class AdvancedSearchControl extends Component {
constructor(props)
{
super(props)
this.state = {
type: undefined,
size: {min: 0, max: 0},
maxSize: 1024 * 1024 * 1024,
sizeEnabled: false,
filesEnabled: false,
files: {min: 0, max: 0},
filesMax: 100,
}
if(this.props.state)
this.state = Object.assign(this.state, this.props.state)
}
setState(val)
{
super.setState(val, (v) => {
if(this.props.onChange)
this.props.onChange(this.state)
})
}
render() {
return (
<div className='column w100p' style={{maxWidth: 750, overflow: 'hidden', padding: '0px 18px 15px'}}>
<SelectField
floatingLabelText="Filter content type"
value={this.state.type}
onChange={(event, index, value) => this.setState({type: value})}
>
<MenuItem value={undefined} primaryText="" />
<MenuItem value='video' primaryText="Video" />
<MenuItem value='audio' primaryText="Audio" />
<MenuItem value='pictures' primaryText="Pictures" />
<MenuItem value='books' primaryText="Books" />
<MenuItem value='application' primaryText="Applications" />
<MenuItem value='archive' primaryText="Archives" />
<MenuItem value='disc' primaryText="Disk Images" />
</SelectField>
<div className='w100p'>
<InputSize value={this.state.size} enabled={this.state.sizeEnabled} maxSize={this.state.maxSize} onChange={({size, maxSize, enabled}) => this.setState({size, maxSize, sizeEnabled: enabled})} />
</div>
<div className='w100p'>
<FilesFilterInput value={this.state.files} filesMax={this.state.filesMax} enabled={this.state.filesEnabled} onChange={({files, filesMax, enabled}) => this.setState({files, filesMax, filesEnabled: enabled})} />
</div>
</div>
);
}
}

136
src/app/search-results.js Normal file
View File

@ -0,0 +1,136 @@
import React, { Component } from 'react';
import TorrentLine from './torrent'
import {List, ListItem} from 'material-ui/List';
import Subheader from 'material-ui/Subheader';
import Divider from 'material-ui/Divider';
import LinearProgress from 'material-ui/LinearProgress';
export default class SearchResults extends Component {
render() {
return (
<List style={{minWidth: '20em'}}>
{
(this.props.torrentsSearchResults && this.props.torrentsSearchResults.length > 0)
|| (this.props.filesSearchResults && this.props.filesSearchResults.length > 0)
?
<div>
<Subheader inset={true}>Search results</Subheader>
<div className='w100p row center' style={{marginTop: '-16px'}}>{this.props.resultSelector}</div>
</div>
:
null
}
{
this.props.torrentsSearchResults && this.props.torrentsSearchResults.length > 0
?
this.props.torrentsSearchResults.map((torrent, index) =>{
return(
<TorrentLine torrent={torrent} key={index} />
);
})
:
null
}
{
this.props.moreTorrentsEnabled && !this.props.moreTorrentsIndicator
?
<div>
<ListItem innerDivStyle={{textAlign: 'center', padding: '1em'}} primaryText={<span>More Torrents</span>} onClick={() => {
if(this.props.onMoreTorrents)
this.props.onMoreTorrents();
}} />
<Divider />
</div>
:
null
}
{
this.props.moreTorrentsIndicator
?
<div style={{padding: '0.8em'}}>
<LinearProgress mode="indeterminate" />
</div>
:
null
}
{
this.props.filesSearchResults && this.props.filesSearchResults.length > 0
?
this.props.filesSearchResults.map((torrent, index) =>{
return(
<TorrentLine torrent={torrent} key={index} />
);
})
:
null
}
{
this.props.moreFilesEnabled && !this.props.moreFilesIndicator
?
<div>
<ListItem innerDivStyle={{textAlign: 'center', padding: '1em'}} primaryText='More Files' onClick={() => {
if(this.props.onMoreFiles)
this.props.onMoreFiles();
}} />
<Divider />
</div>
:
null
}
{
this.props.moreFilesIndicator
?
<div style={{padding: '0.8em'}}>
<LinearProgress mode="indeterminate" />
</div>
:
null
}
{
this.props.torrentsSearchResults && this.props.torrentsSearchResults.length == 0
&& this.props.filesSearchResults && this.props.filesSearchResults.length == 0
&& !this.props.currentSearching
?
<div className='row inline'>
<svg style={{fill: 'grey', height: '30px'}} viewBox="0 0 264.695 264.695">
<g>
<path d="M219.171,216.785c-4.762,0-10.322,2.3-16.672,6.881l-35.211-12.968l35.734-12.978
c6.003,3.888,11.558,5.833,16.682,5.833c5.639,0,9.347-2.917,11.117-8.733c0.351-1.235,0.527-2.57,0.527-3.981
c0-7.397-4.766-11.378-14.295-11.9c3.876-3.882,5.828-7.687,5.828-11.392c0-3.871-2.039-7.149-6.092-9.797
c-2.118-1.049-4.325-1.584-6.615-1.584c-7.769,0-13.064,6.258-15.887,18.797l-61.941,23.039l-61.94-22.504
c-2.823-12.885-8.125-19.332-15.885-19.332c-2.293,0-4.501,0.535-6.62,1.584c-3.876,2.647-5.82,5.926-5.82,9.797
c0,3.705,1.944,7.51,5.82,11.392c-9.701,0.522-14.555,4.503-14.555,11.901c0,1.41,0.179,2.746,0.526,3.98
c1.946,5.816,5.651,8.733,11.122,8.733c5.113,0,10.671-1.945,16.677-5.832l35.998,12.977l-35.476,12.698
c-6.175-4.406-11.637-6.611-16.402-6.611c-5.654,0-9.623,2.918-11.919,8.733c-0.348,1.235-0.526,2.553-0.526,3.975
c0,7.405,4.853,11.385,14.555,11.907c-3.876,3.883-5.82,7.688-5.82,11.393c0,3.869,1.944,7.134,5.82,9.797
c2.477,1.412,4.854,2.105,7.153,2.105c7.227,0,12.443-6.176,15.619-18.525l61.673-22.504l61.678,22.504
c3.178,12.35,8.475,18.525,15.882,18.525c2.121,0,4.407-0.693,6.884-2.105c4.052-2.663,6.092-5.928,6.092-9.797
c0-3.705-1.953-7.51-5.828-11.393c9.528-0.522,14.295-4.502,14.295-11.907c0-1.422-0.177-2.739-0.527-3.975
C228.702,219.702,224.82,216.785,219.171,216.785z"/>
<path d="M48.436,128.904c9.703,11.114,23.379,19.242,41.035,24.346v3.986c0,4.936,1.672,9.086,5.025,12.433
c3.35,3.358,7.498,5.211,12.441,5.563c5.116,0.357,8.905-0.528,11.378-2.646c3.879,2.817,8.204,4.229,12.974,4.229
c4.41,0,8.474-1.316,12.175-3.963c2.471,1.934,6.087,2.738,10.856,2.381c4.937-0.528,9.089-2.426,12.44-5.689
c3.35-3.281,5.025-7.371,5.025-12.307v-2.91c19.057-4.945,33.795-13.237,44.21-24.898c10.059-11.109,15.087-24.253,15.087-39.435
c0-3.359-0.355-6.886-1.063-10.597c-3.525-22.571-13.938-41.201-31.229-55.844C180.612,7.856,158.464,0,132.347,0
c-26.123,0-48.27,7.767-66.44,23.282C48.61,38.118,38.289,56.825,34.937,79.396c-0.709,3.711-1.064,7.238-1.064,10.597
C33.873,104.817,38.724,117.778,48.436,128.904L48.436,128.904z M152.865,60.749c5.206-6.085,11.514-9.13,18.922-9.13
c7.592,0,13.986,3.045,19.194,9.13c5.2,6.076,7.81,13.446,7.81,22.087c0,8.649-2.609,16.021-7.81,22.108
c-5.208,6.097-11.603,9.13-19.194,9.13c-7.408,0-13.716-3.033-18.922-9.13c-5.211-6.087-7.814-13.459-7.814-22.108
C145.05,74.195,147.654,66.825,152.865,60.749z M124.805,121.428c2.556-3.307,5.065-4.968,7.542-4.968
c2.47,0,4.802,1.831,7.012,5.509c2.205,3.662,3.317,7.145,3.317,10.469c0,5.062-3.361,7.581-10.067,7.581
c-4.414,0-7.677-1.136-9.792-3.396c-1.237-1.411-1.849-3.147-1.849-5.249C120.969,128.065,122.245,124.752,124.805,121.428z
M71.465,60.749c5.295-6.085,11.65-9.13,19.059-9.13c7.406,0,13.762,3.045,19.06,9.13c5.296,6.076,7.948,13.446,7.948,22.087
c0,8.649-2.651,16.021-7.948,22.108c-5.297,6.097-11.654,9.13-19.06,9.13c-7.409,0-13.764-3.033-19.059-9.13
c-5.292-6.087-7.944-13.459-7.944-22.108C63.521,74.195,66.173,66.825,71.465,60.749z"/>
</g>
</svg>
<div className='fs0-85 pad0-75' style={{color: 'grey'}}>no torrents for this search request were found</div>
</div>
:
null
}
</List>
);
}
}

407
src/app/search.js Normal file
View File

@ -0,0 +1,407 @@
import React, { Component } from 'react';
import SearchResults from './search-results'
import AdvancedSearch from './search-advanced-controls'
import TextField from 'material-ui/TextField';
import RaisedButton from 'material-ui/RaisedButton';
import RefreshIndicator from 'material-ui/RefreshIndicator';
import Checkbox from 'material-ui/Checkbox';
import Visibility from 'material-ui/svg-icons/action/visibility';
import VisibilityOff from 'material-ui/svg-icons/action/visibility-off';
import AddIcon from 'material-ui/svg-icons/content/add';
import RemoveIcon from 'material-ui/svg-icons/content/remove';
import SelectField from 'material-ui/SelectField';
import MenuItem from 'material-ui/MenuItem';
import formatBytes from './format-bytes'
let session;
class TorrentsStatistic extends Component {
constructor(props)
{
super(props)
this.stats = props.stats || {}
}
componentDidMount()
{
this.newTorrentFunc = (torrent) => {
this.stats.size += torrent.size;
this.stats.torrents++;
this.stats.files += torrent.files;
this.forceUpdate()
}
window.torrentSocket.on('newTorrent', this.newTorrentFunc);
}
componentWillUnmount()
{
if(this.newTorrentFunc)
window.torrentSocket.off('newTorrent', this.newTorrentFunc);
}
render()
{
return (
<div className='fs0-75 pad0-75' style={{color: 'rgba(0, 0, 0, 0.541176)'}}>you have information about {this.stats.torrents} torrents and around {this.stats.files} files and { formatBytes(this.stats.size, 1) } of data</div>
)
}
}
export default class Search extends Component {
constructor(props)
{
super(props)
this.state = {
searchingIndicator: false,
safeSearchText: 'safe search enabled',
safeSearchColor: 'rgb(0, 188, 212)',
moreTorrentsIndicator: false,
moreFilesIndicator: false,
orderBy: null,
orderDesc: false,
advancedSearch: false,
}
this.searchLimit = 10
this.advanced = {}
this.searchError = undefined;
if(session)
{
this.searchTorrents = session.searchTorrents;
this.searchFiles = session.searchFiles;
this.moreSearchTorrents = session.moreSearchTorrents;
this.moreSearchFiles = session.moreSearchFiles;
this.currentSearch = session.currentSearch;
this.searchValue = session.searchValue;
Object.assign(this.state, this.setSafeSearch(session.notSafeSearch))
this.state.orderBy = session.orderBy;
this.state.orderDesc = session.orderDesc;
this.state.advancedSearch = session.advancedSearch;
this.advanced = session.advanced;
this.searchError = session.searchError;
}
}
search(oldSearch) {
this.setState({
searchingIndicator: true
});
this.searchTorrents = [];
this.moreSearchTorrents = true;
this.searchFiles = [];
this.moreSearchFiles = true;
this.currentSearch = this.searchValue;
let queries = 2;
let searchTorrentsParams = {
limit: this.searchLimit,
safeSearch: !this.notSafeSearch,
orderBy: this.state.orderBy,
orderDesc: this.state.orderDesc,
};
if(this.state.advancedSearch && this.advanced)
searchTorrentsParams = Object.assign(searchTorrentsParams, this.advanced);
window.torrentSocket.emit('searchTorrent', oldSearch ? this.currentSearch : this.searchValue, searchTorrentsParams, window.customLoader((torrents) => {
if(torrents) {
this.searchTorrents = torrents;
if(torrents.length != this.searchLimit)
this.moreSearchTorrents = false;
}
else
{
this.moreSearchTorrents = false;
}
if(--queries == 0) {
this.setState({
searchingIndicator: false
});
} else {
this.forceUpdate();
}
}));
let searchFilesParams = {
limit: this.searchLimit,
safeSearch: !this.notSafeSearch,
orderBy: this.state.orderBy,
orderDesc: this.state.orderDesc,
};
if(this.state.advancedSearch && this.advanced)
searchFilesParams = Object.assign(searchFilesParams, this.advanced);
window.torrentSocket.emit('searchFiles', oldSearch ? this.currentSearch : this.searchValue, searchFilesParams, window.customLoader((torrents) => {
if(torrents) {
this.searchFiles = torrents;
let files = 0;
torrents.forEach((torrent) => {
if(torrent.path && torrent.path.length > 0)
files += torrent.path.length
});
if(files != this.searchLimit)
this.moreSearchFiles = false;
}
else
{
this.moreSearchFiles = false;
}
if(--queries == 0) {
this.setState({
searchingIndicator: false
});
} else {
this.forceUpdate();
}
}));
}
moreTorrents() {
this.setState({moreTorrentsIndicator: true});
window.torrentSocket.emit('searchTorrent', this.currentSearch, {
index: this.searchTorrents.length,
limit: this.searchLimit,
safeSearch: !this.notSafeSearch,
orderBy: this.state.orderBy,
orderDesc: this.state.orderDesc,
}, window.customLoader((torrents) => {
if(torrents) {
this.searchTorrents = this.searchTorrents.concat(torrents);
if(torrents.length != this.searchLimit)
this.moreSearchTorrents = false;
this.setState({moreTorrentsIndicator: false});
}
}));
}
moreFiles() {
let index = 0;
this.searchFiles.forEach((torrent) => {
if(torrent.path && torrent.path.length > 0)
index += torrent.path.length;
});
this.setState({moreFilesIndicator: true});
window.torrentSocket.emit('searchFiles', this.currentSearch, {
index: index,
limit: this.searchLimit,
safeSearch: !this.notSafeSearch,
orderBy: this.state.orderBy,
orderDesc: this.state.orderDesc,
}, window.customLoader((torrents) => {
if(torrents) {
this.searchFiles = this.searchFiles.concat(torrents);
let files = 0;
torrents.forEach((torrent) => {
if(torrent.path && torrent.path.length > 0)
files += torrent.path.length
});
if(files != this.searchLimit)
this.moreSearchFiles = false;
this.setState({moreFilesIndicator: false});
}
}));
}
componentDidMount() {
this.newStatisticFunc = (statistic) => {
if(statistic) {
this.stats = statistic;
this.forceUpdate();
}
};
window.torrentSocket.emit('statistic', window.customLoader(this.newStatisticFunc));
window.torrentSocket.on('newStatistic', this.newStatisticFunc);
}
componentWillUnmount()
{
if(this.newStatisticFunc)
window.torrentSocket.off('newStatistic', this.newStatisticFunc);
session = {
searchTorrents: this.searchTorrents,
searchFiles: this.searchFiles,
moreSearchTorrents: this.moreSearchTorrents,
moreSearchFiles: this.moreSearchFiles,
currentSearch: this.currentSearch,
searchValue: this.searchValue,
notSafeSearch: this.notSafeSearch,
orderBy: this.state.orderBy,
orderDesc: this.state.orderDesc,
advancedSearch: this.state.advancedSearch,
advanced: this.advanced,
searchError: this.searchError,
}
}
setSafeSearch(ch) {
this.notSafeSearch = ch;
if(ch)
{
return {safeSearchText: 'safe search disabled', safeSearchColor: '#EC407A'}
}
else
{
return {safeSearchText: 'safe search enabled', safeSearchColor: 'rgb(0, 188, 212)'}
}
}
render() {
const style = {
refresh: {
display: 'inline-block',
position: 'relative',
},
};
const orderText = (text, field) => {
if(field !== this.state.orderBy)
return text;
if(this.state.orderDesc)
return text + ' ⇩'
else
return text + ' ⇧'
}
return (
<div className="column w100p center">
<div className='row inline w100p pad0-75' style={{maxWidth: '30em'}}>
<TextField
hintText="Search torrent or file"
floatingLabelText="What to search?"
fullWidth={true}
ref='searchInput'
defaultValue={this.searchValue}
errorText={this.searchError}
onKeyPress={(e) => {
if (e.key === 'Enter') {
this.search();
}
}}
onChange={e => {
this.searchValue = e.target.value
if(this.searchValue.length < 3 && this.searchValue.length > 0)
this.searchError = 'too short string for search';
else
this.searchError = undefined;
this.forceUpdate()
}}
/>
<RaisedButton style={{marginTop: '15px', marginLeft: '10px'}} label="Search" primary={true} onClick={() =>{
this.search()
}} />
</div>
<div className='row w100p center wrap' style={{padding: '0 8px'}}>
<div style={{padding: '0px 17px'}}>
<Checkbox
ref='safeSearch'
checked={this.notSafeSearch ? true : false}
checkedIcon={<Visibility />}
uncheckedIcon={<VisibilityOff />}
label={<span className='text-nowrap' style={{fontSize: '0.87em', transition: '0.1s', color: this.state.safeSearchColor}}>{this.state.safeSearchText}</span>}
iconStyle={{fill: this.state.safeSearchColor}}
onCheck={(ev, ch) => {
this.setState(this.setSafeSearch(ch));
}}
style={{paddingBottom: '0.8em'}}
/>
</div>
<div style={{padding: '0px 17px'}}>
<Checkbox
ref='advancedSearch'
checked={this.state.advancedSearch}
checkedIcon={<RemoveIcon />}
uncheckedIcon={<AddIcon />}
label={<span className='text-nowrap' style={{fontSize: '0.87em', transition: '0.1s', color: 'black'}}>advanced search</span>}
iconStyle={{fill: 'black'}}
onCheck={(ev, ch) => {
this.setState({advancedSearch: ch});
}}
style={{paddingBottom: '0.8em'}}
/>
</div>
</div>
{
this.state.advancedSearch
&&
<AdvancedSearch onChange={(state) => {
this.advanced = state;
}} state={this.advanced} />
}
{
this.stats
&&
<TorrentsStatistic stats={this.stats} />
}
{
this.state.searchingIndicator
?
<div className='pad1'>
<RefreshIndicator
size={50}
left={0}
top={0}
loadingColor="#FF9800"
status="loading"
style={style.refresh}
/>
</div>
:
null
}
<SearchResults
torrentsSearchResults={this.searchTorrents}
filesSearchResults={this.searchFiles}
currentSearching={this.state.searchingIndicator}
resultSelector={
<SelectField
floatingLabelText="Sort by"
floatingLabelFixed={true}
value={this.state.orderBy}
onChange={(event, index, value) => {
event.preventDefault(); // fix overclick on torrent
if(value === 'none') {
this.setState({orderBy: null}, () => {
this.search(true)
})
return;
}
if(value === this.state.orderBy)
{
this.setState({orderDesc: !this.state.orderDesc}, () => {
this.search(true)
})
return;
}
this.setState({
orderBy: value,
orderDesc: (value === 'seeders' || value === 'completed' || value === 'added') ? true : this.state.orderDesc
}, () => {
this.search(true)
})
}}
>
<MenuItem value='none' primaryText={'None'} />
<MenuItem value='seeders' primaryText={orderText('Seeders', 'seeders')} />
<MenuItem value='name' primaryText={orderText('Name', 'name')} />
<MenuItem value='files' primaryText={orderText('Files', 'files')} />
<MenuItem value='size' primaryText={orderText('Size', 'size')} />
<MenuItem value='added' primaryText={orderText('Added date', 'added')} />
<MenuItem value='completed' primaryText={orderText('Completed', 'completed')} />
</SelectField>
}
moreTorrentsEnabled={this.moreSearchTorrents && !this.state.searchingIndicator}
moreFilesEnabled={this.moreSearchFiles && !this.state.searchingIndicator}
onMoreTorrents={() => this.moreTorrents()}
onMoreFiles={() => this.moreFiles()}
moreTorrentsIndicator={this.state.moreTorrentsIndicator}
moreFilesIndicator={this.state.moreFilesIndicator}
/>
</div>
);
}
}

78
src/app/top-page.js Normal file
View File

@ -0,0 +1,78 @@
import React from 'react';
import Page from './page';
import Footer from './footer';
import { Header } from './index-page'
import TorrentLine from './torrent'
import {List} from 'material-ui/List';
import Subheader from 'material-ui/Subheader';
import RaisedButton from 'material-ui/RaisedButton';
export default class TopPage extends Page {
constructor(props) {
super(props)
this.setTitle('Rats On The Boat - Torrents top');
this.topTorrents = {};
this.types = ['main', 'week', 'hours', 'month', 'video', 'audio', 'books', 'pictures', 'application', 'archive']
this.descriptions = {
main: 'All',
video: 'Video',
audio: 'Audio/Music',
books: 'Books',
pictures: 'Pictures/Images',
application: 'Applications/Games',
archive: 'Archives',
week: 'Last week',
hours: 'Last 24 hours',
month: 'Last month'
}
}
componentDidMount()
{
super.componentDidMount();
for(const type of this.types)
{
window.torrentSocket.emit('topTorrents', type == 'main' ? null : type, window.customLoader((data) => {
this.topTorrents[type] = data;
this.forceUpdate()
}))
}
}
render() {
return (
<div>
<Header />
<div className='column center w100p pad0-75'>
<RaisedButton label="Back to main page" primary={true} onClick={() => {
window.router('/')
}} />
{
this.types.map((type, index) => {
const torrents = this.topTorrents[type];
if(!torrents)
return null;
return (
<List key={index} style={{paddingBottom: '70px'}} className='animated recent-torrents'>
<Subheader inset={true}>
{
this.descriptions[type]
}
</Subheader>
{
torrents.map((torrent, index) => {
return <TorrentLine key={index} torrent={torrent} />
})
}
</List>
)
})
}
<Footer />
</div>
</div>
);
}
}

415
src/app/torrent-page.js Normal file
View File

@ -0,0 +1,415 @@
import React from 'react';
import Page from './page';
import formatBytes from './format-bytes'
import Footer from './footer';
import {List, ListItem} from 'material-ui/List';
import Subheader from 'material-ui/Subheader';
import Divider from 'material-ui/Divider';
import {Tabs, Tab} from 'material-ui/Tabs';
import ActionInfo from 'material-ui/svg-icons/action/info';
import RaisedButton from 'material-ui/RaisedButton';
import FontIcon from 'material-ui/FontIcon';
import FileFolder from 'material-ui/svg-icons/file/folder';
import NoImage from './images/no-image-icon.png'
var moment = require('moment');
import RefreshIndicator from 'material-ui/RefreshIndicator';
let rating = require('./rating');
import LinearProgress from 'material-ui/LinearProgress';
import {fileTypeDetect} from './content'
import {contentIcon} from './torrent'
let buildFilesTree = (filesList) => {
let rootTree = {
__sizeBT: 0
};
filesList.forEach((file) => {
let pathTree = file.path.split('/');
let currentItem = rootTree;
pathTree.forEach((pathItem) => {
if(!(pathItem in currentItem))
{
currentItem[pathItem] = {
__sizeBT: 0
}
}
currentItem = currentItem[pathItem]
currentItem.__sizeBT += file.size;
})
rootTree.__sizeBT += file.size;
});
return rootTree;
}
const treeToTorrentFiles = (tree) => {
let arr = [];
for(let file in tree)
{
if(file == '__sizeBT')
continue;
arr.push(<ListItem
key={file}
primaryText={file}
secondaryText={formatBytes(tree[file].__sizeBT)}
nestedItems={treeToTorrentFiles(tree[file])}
primaryTogglesNestedList={true}
innerDivStyle={{wordBreak: 'break-word'}}
leftIcon={tree[file] && Object.keys(tree[file]).length > 1 ? <FileFolder /> : contentIcon(fileTypeDetect({path: file}))}
/>);
}
return arr;
}
const TorrentFiles = (props) => {
let filesList = props.torrent.filesList;
let tree = buildFilesTree(filesList);
return (
<List className='w100p'>
{
filesList.length > 0
?
<div className='w100p'>
<Subheader inset={true}>Content of the torrent:</Subheader>
{treeToTorrentFiles(tree)}
</div>
:
<div className='column center'>
<span className='pad0-75'>Processing files...</span>
<LinearProgress mode="indeterminate" />
</div>
}
</List>
);
};
const TorrentInformation = (props) => {
let torrent = props.torrent;
return (
<List className='w100p'>
<Subheader inset={true}>Information about torrent</Subheader>
<ListItem
//leftAvatar={<Avatar icon={<ActionAssignment />} backgroundColor={blue500} />}
rightIcon={<ActionInfo />}
primaryText="Torrent Name"
secondaryText={<span className='break-word' style={{whiteSpace: 'normal'}}>{torrent.name}</span>}
/>
<ListItem
// leftAvatar={<Avatar icon={<EditorInsertChart />} backgroundColor={yellow600} />}
rightIcon={<ActionInfo />}
primaryText="Torrent Size"
secondaryText={formatBytes(torrent.size)}
/>
<ListItem
// leftAvatar={<Avatar icon={<EditorInsertChart />} backgroundColor={yellow600} />}
rightIcon={<ActionInfo />}
primaryText="Torrent contains files"
secondaryText={torrent.files}
onClick={() => {
if(!props.parent)
return
props.parent.setState({
value: 'files'
})
}}
/>
<ListItem
// leftAvatar={<Avatar icon={<EditorInsertChart />} backgroundColor={yellow600} />}
rightIcon={<ActionInfo />}
primaryText="Indexed/Added torrent date"
secondaryText={moment(torrent.added).format('MMMM Do YYYY, h:mm:ss')}
/>
<ListItem
// leftAvatar={<Avatar icon={<EditorInsertChart />} backgroundColor={yellow600} />}
rightIcon={<ActionInfo />}
primaryText="Content type"
secondaryText={torrent.contentType || 'unknown'}
/>
<ListItem
// leftAvatar={<Avatar icon={<EditorInsertChart />} backgroundColor={yellow600} />}
rightIcon={<ActionInfo />}
primaryText="Category"
secondaryText={torrent.contentCategory || 'unknown'}
/>
</List>
);
}
export default class TorrentPage extends Page {
constructor(props) {
super(props);
this.state = {
value: 'info',
searchingIndicator: false,
voting: false,
voted: false,
};
this.setTitle('Information about torrent');
}
changeTab(tab) {
if(this.state.value != tab) {
this.setState({
value: tab
});
console.log('change');
}
}
onSwipeRight() {
this.changeTab('files');
}
onSwipeLeft() {
this.changeTab('info');
}
handleChange = (value) => {
if(value == 'main') {
window.router('/');
return;
}
this.setState({
value: value,
});
};
getTorrentInfo() {
window.torrentSocket.emit('torrent', this.props.hash, {files: true}, window.customLoader((data) => {
if(data) {
this.torrent = data
this.setTitle(this.torrent.name + ' - RatsOnTheBoat.org');
if(this.torrent.contentCategory == 'xxx') {
this.setMetaTag('robots', 'noindex');
}
//this.forceUpdate(); // вызывается через searchingIndicator
// Получаем более новую статистику пира
if((new Date).getTime() - this.torrent.trackersChecked > 10 * 60 * 1000) {
window.torrentSocket.emit('checkTrackers', this.torrent.hash);
}
}
}, () => {
this.setState({
searchingIndicator: true
});
}, () => {
this.setState({
searchingIndicator: false
});
}));
}
componentDidMount() {
super.componentDidMount();
this.getTorrentInfo();
this.filesUpdated = (hash) => {
if(this.props.hash != hash)
return;
this.getTorrentInfo();
}
window.torrentSocket.on('filesReady', this.filesUpdated);
this.trackerUpdate = (info) => {
if(this.props.hash != info.hash)
return;
if(!this.torrent)
return;
Object.assign(this.torrent, info);
this.forceUpdate();
}
window.torrentSocket.on('trackerTorrentUpdate', this.trackerUpdate);
this.onVote = ({hash, good, bad}) => {
if(this.props.hash != hash)
return;
if(!this.torrent)
return;
this.torrent.good = good;
this.torrent.bad = bad;
this.forceUpdate();
}
window.torrentSocket.on('vote', this.onVote);
}
componentWillUnmount() {
if(this.filesUpdated)
window.torrentSocket.off('filesReady', this.filesUpdated);
if(this.trackerUpdate)
window.torrentSocket.off('trackerTorrentUpdate', this.trackerUpdate);
if(this.onVote)
window.torrentSocket.off('vote', this.onVote);
if(this.torrent && this.torrent.contentCategory == 'xxx') {
this.removeMetaTag('robots');
}
}
vote(good) {
if(!this.torrent)
return;
this.setState({
voting: true
});
window.torrentSocket.emit('vote', this.torrent.hash, !!good, window.customLoader((success) => {
this.setState({
voted: true,
voting: false
});
}));
}
render() {
const style = {
refresh: {
display: 'inline-block',
position: 'relative',
},
};
if(this.state.searchingIndicator) {
return (
<div className='pad1 w100p column center'>
<RefreshIndicator
size={50}
left={0}
top={0}
loadingColor="#FF9800"
status="loading"
style={style.refresh}
/>
</div>
);
}
let torrentRating;
if(this.torrent) {
torrentRating = Math.round(rating(this.torrent.good, this.torrent.bad) * 100);
}
return (
<div className="w100p column center">
{
this.torrent
?
<Tabs
className='w100p'
value={this.state.value}
onChange={this.handleChange}
>
<Tab label="Back to main" value="main" />
<Tab label="Information" value="info" >
<div className='column w100p'>
<div className='row w100p torrent-information-row'>
<div className='info-table'>
<TorrentInformation torrent={this.torrent} parent={this} />
</div>
<div style={{flexBasis: '40%'}} className='column center w100p'>
<img src={NoImage} className='pad0-75' style={{height: '200px'}} />
<RaisedButton
href={`magnet:?xt=urn:btih:${this.torrent.hash}`}
target="_self"
label="Download"
secondary={true}
onClick={(e) => {
e.preventDefault();
window.open(`magnet:?xt=urn:btih:${this.torrent.hash}`, '_self')
}}
icon={<svg fill='white' viewBox="0 0 24 24"><path d="M17.374 20.235c2.444-2.981 6.626-8.157 6.626-8.157l-3.846-3.092s-2.857 3.523-6.571 8.097c-4.312 5.312-11.881-2.41-6.671-6.671 4.561-3.729 8.097-6.57 8.097-6.57l-3.092-3.842s-5.173 4.181-8.157 6.621c-2.662 2.175-3.76 4.749-3.76 7.24 0 5.254 4.867 10.139 10.121 10.139 2.487 0 5.064-1.095 7.253-3.765zm4.724-7.953l-1.699 2.111-1.74-1.397 1.701-2.114 1.738 1.4zm-10.386-10.385l1.4 1.738-2.113 1.701-1.397-1.74 2.11-1.699z"/></svg>}
/>
<div className='fs0-75 pad0-75 center column' style={{color: 'rgba(0, 0, 0, 0.541176)'}}><div>BTIH:</div><div>{this.torrent.hash}</div></div>
{
this.torrent.seeders || this.torrent.leechers || this.torrent.completed
?
<div className='fs0-85 pad0-75 center column'>
<div className='pad0-25' style={{color: '#00C853'}}>seeders: {this.torrent.seeders}</div>
<div className='pad0-25' style={{color: '#AA00FF'}}>leechers: {this.torrent.leechers}</div>
<div className='pad0-25' style={{color: '#FF6D00'}}>completed: {this.torrent.completed}</div>
</div>
:
null
}
{
!this.state.voted && !this.state.voting
?
<div className='row pad0-25'>
<RaisedButton
label={`Good (${this.torrent.good})`}
labelColor="white"
backgroundColor="#00C853"
icon={
<svg viewBox="0 0 489.543 489.543" fill="white">
<g>
<path d="M270.024,0c-22.6,0-15,48.3-15,48.3s-48.3,133.2-94.5,168.7c-9.9,10.4-16.1,21.9-20,31.3l0,0l0,0
c-0.9,2.3-1.7,4.5-2.4,6.5c-3.1,6.3-9.7,16-23.8,24.5l46.2,200.9c0,0,71.5,9.3,143.2,7.8c28.7,2.3,59.1,2.5,83.3-2.7
c82.2-17.5,61.6-74.8,61.6-74.8c44.3-33.3,19.1-74.9,19.1-74.9c39.4-41.1,0.7-75.6,0.7-75.6s21.3-33.2-6.2-58.3
c-34.3-31.4-127.4-10.5-127.4-10.5l0,0c-6.5,1.1-13.4,2.5-20.8,4.3c0,0-32.2,15,0-82.7C346.324,15.1,292.624,0,270.024,0z"/>
<path d="M127.324,465.7l-35-166.3c-2-9.5-11.6-17.3-21.3-17.3h-66.8l-0.1,200.8h109.1C123.024,483,129.324,475.2,127.324,465.7z"
/>
</g>
</svg>
}
onClick={() => this.vote(true)}
/>
<RaisedButton
style={{marginLeft: '9px'}}
label={`Bad (${this.torrent.bad})`}
labelColor="white"
backgroundColor="#D50000"
icon={
<svg viewBox="0 0 487.643 487.643" fill="white">
<g>
<path d="M113.869,209.443l46-200.1c0,0,71.2-9.3,142.6-7.8c28.5-2.3,58.9-2.5,83,2.7c81.9,17.4,61.4,74.5,61.4,74.5
c44.2,33.2,19,74.6,19,74.6c39.2,41,0.7,75.3,0.7,75.3s21.2,33-6.1,58c-34.2,31.2-126.9,10.5-126.9,10.5l0,0
c-6.4-1.1-13.3-2.5-20.7-4.2c0,0-32.1-15,0,82.4s-21.4,112.3-43.9,112.3s-15-48.1-15-48.1s-48.1-132.7-94.1-168
c-9.9-10.4-16.1-21.8-19.9-31.2l0,0l0,0c-0.9-2.3-1.7-4.5-2.4-6.5C134.469,227.543,127.869,217.843,113.869,209.443z
M70.869,206.643c9.7,0,19.2-7.7,21.2-17.2l34.8-165.6c2-9.5-4.3-17.2-14-17.2H4.169l0.1,200H70.869z"/>
</g>
</svg>
}
onClick={() => this.vote(false)}
/>
</div>
:
this.state.voting
?
<div>voting...</div>
:
<div>Thank you for voting!</div>
}
{
this.torrent.good > 0 || this.torrent.bad > 0
?
<div className='w100p' style={{padding: '7px 35px', marginTop: '10px'}}>
<LinearProgress
mode="determinate"
value={torrentRating}
color={torrentRating >= 50 ? '#00E676' : '#FF3D00'}
style={{
height: '5px',
}}
/>
<div className='row center pad0-75 fs0-85' style={{color: torrentRating >= 50 ? '#00E676' : '#FF3D00'}}>Torrent rating: {torrentRating}%</div>
</div>
:
null
}
</div>
</div>
</div>
</Tab>
<Tab label="Files" value="files" >
<TorrentFiles torrent={this.torrent} />
</Tab>
</Tabs>
:
null
}
<Footer />
</div>
);
}
}

247
src/app/torrent.js Normal file
View File

@ -0,0 +1,247 @@
import React, { Component } from 'react';
import formatBytes from './format-bytes'
import {ListItem} from 'material-ui/List';
import Divider from 'material-ui/Divider';
const contentIcon = (type, category) => {
if(category == 'xxx')
{
return (
<svg viewBox="0 0 18.282 18.282" fill="grey">
<g>
<path d="M16.435,3.832H1.847C0.827,3.832,0,4.659,0,5.678v6.925c0,1.021,0.827,1.848,1.847,1.848h14.588
c1.021,0,1.847-0.827,1.847-1.848V5.678C18.282,4.659,17.455,3.832,16.435,3.832z M3.194,7.123H2.583v4.042h0.611v0.54H1.876V6.583
h1.318V7.123z M6.197,10.986l-0.392-0.784C5.644,9.9,5.541,9.676,5.419,9.425H5.406c-0.09,0.251-0.199,0.476-0.334,0.777
l-0.36,0.784H3.593l1.254-2.191L3.638,6.654h1.125l0.379,0.791C5.27,7.709,5.367,7.921,5.47,8.165h0.013
c0.103-0.276,0.187-0.47,0.296-0.72l0.366-0.791h1.118L6.042,8.768l1.285,2.218C7.327,10.986,6.197,10.986,6.197,10.986z
M10.068,10.986l-0.392-0.784C9.515,9.9,9.412,9.676,9.29,9.425H9.277c-0.091,0.251-0.2,0.476-0.335,0.777l-0.359,0.784H7.464
l1.253-2.191L7.509,6.654h1.125l0.379,0.791c0.128,0.264,0.225,0.476,0.328,0.72h0.013c0.103-0.276,0.186-0.47,0.295-0.72
l0.366-0.791h1.119L9.913,8.768l1.284,2.218C11.197,10.986,10.068,10.986,10.068,10.986z M13.94,10.986l-0.393-0.784
c-0.16-0.302-0.263-0.526-0.386-0.777h-0.012c-0.091,0.251-0.2,0.476-0.335,0.777l-0.36,0.784h-1.117l1.253-2.191l-1.209-2.141
h1.125l0.379,0.791c0.129,0.264,0.226,0.476,0.328,0.72h0.013c0.104-0.276,0.187-0.47,0.296-0.72l0.366-0.791h1.118l-1.221,2.114
l1.286,2.218C15.071,10.986,13.94,10.986,13.94,10.986z M16.756,11.705h-1.311v-0.54h0.611V7.116h-0.611v-0.54h1.311V11.705z"/>
</g>
</svg>
)
}
switch(type)
{
case 'video':
return (
<svg viewBox="0 0 491.858 491.858" fill="grey">
<path d="M357.714,423.331c0,9.328-10.676,16.891-23.847,16.891H23.847C10.676,440.222,0,432.659,0,423.331V203.735
c0-9.33,10.676-16.892,23.847-16.892h310.02c13.171,0,23.847,7.564,23.847,16.892V423.331L357.714,423.331z"/>
<circle cx="89.428" cy="118.706" r="59.619"/>
<circle cx="253.381" cy="103.801" r="74.524"/>
<path d="M491.858,447.677c0,0-1.986,14.904-15.899,14.904c-13.912,0-103.34-83.42-103.34-94.397V258.882
c0-10.976,87.443-94.398,103.34-94.398c15.899,0,15.899,14.905,15.899,14.905V447.677z"/>
</svg>
)
case 'audio':
return (
<svg viewBox="0 0 46 46" fill="grey">
<path d="M28.38,0c-0.551,0-1.097,0.153-1.579,0.444c-0.046,0.027-0.09,0.059-0.13,0.093L13.121,12H2.487c-0.553,0-1,0.447-1,1v19
c0,0.553,0.447,1,1,1h10.61L26.64,45.436c0.05,0.046,0.104,0.086,0.161,0.12C27.284,45.847,27.83,46,28.38,46
c1.713,0,3.106-1.416,3.106-3.156V3.156C31.487,1.416,30.093,0,28.38,0z M14.487,31c0,0.553-0.447,1-1,1s-1-0.447-1-1v-4
c0-0.553,0.447-1,1-1s1,0.447,1,1V31z M14.487,18c0,0.553-0.447,1-1,1s-1-0.447-1-1v-4c0-0.553,0.447-1,1-1s1,0.447,1,1V18z"/>
<path d="M44.513,22.5c0-5.972-4.009-11.302-9.749-12.962c-0.533-0.151-1.084,0.152-1.238,0.684
c-0.153,0.53,0.152,1.085,0.684,1.238c4.889,1.413,8.304,5.953,8.304,11.04s-3.415,9.627-8.304,11.04
c-0.531,0.153-0.837,0.708-0.684,1.238c0.127,0.438,0.526,0.723,0.961,0.723c0.092,0,0.185-0.013,0.277-0.039
C40.504,33.802,44.513,28.472,44.513,22.5z"/>
</svg>
)
case 'pictures':
return (
<svg viewBox="0 0 58 58" fill="grey">
<path d="M57,6H1C0.448,6,0,6.447,0,7v44c0,0.553,0.448,1,1,1h56c0.552,0,1-0.447,1-1V7C58,6.447,57.552,6,57,6z M16,17
c3.071,0,5.569,2.498,5.569,5.569c0,3.07-2.498,5.568-5.569,5.568s-5.569-2.498-5.569-5.568C10.431,19.498,12.929,17,16,17z
M52.737,35.676c-0.373,0.406-1.006,0.435-1.413,0.062L40.063,25.414l-9.181,10.054l4.807,4.807c0.391,0.391,0.391,1.023,0,1.414
s-1.023,0.391-1.414,0L23.974,31.389L7.661,45.751C7.471,45.918,7.235,46,7,46c-0.277,0-0.553-0.114-0.751-0.339
c-0.365-0.415-0.325-1.047,0.09-1.412l17.017-14.982c0.396-0.348,0.994-0.329,1.368,0.044l4.743,4.743l9.794-10.727
c0.179-0.196,0.429-0.313,0.694-0.325c0.264-0.006,0.524,0.083,0.72,0.262l12,11C53.083,34.636,53.11,35.269,52.737,35.676z"/>
</svg>
)
case 'application':
return (
<svg viewBox="0 0 483.85 483.85" fill="grey">
<path d="M471.325,211.856l-56.9-56.9c-23.4-23.4-9.1-48.1,16.4-49.6c42-2.6,65.6-47.4,31.3-84.7c-37.3-34.2-81.9-10.7-84.5,31.2
c-1.6,25.5-26.5,39.9-49.8,16.6l-55.7-55.7c-16.7-16.7-43.8-16.7-60.5,0l-56.4,56.4c-23.4,23.4-48.2,8.9-49.8-16.6
c-2.6-42-47.6-65.9-84.9-31.6c-34.4,37.4-10.5,82.4,31.5,85c25.5,1.6,40,26.5,16.7,49.9l-56.2,56.1c-16.7,16.7-16.7,43.8,0,60.5
l55.7,55.7c23.4,23.3,9.5,47.6-16,49.2c-42,2.6-65.5,47.3-31.2,84.6c37.3,34.3,81.8,10.9,84.4-31.1c1.6-25.5,26-39.5,49.4-16.2
l56.2,56.2c17,17,44.8,17,61.8,0.1l39.4-39.4l16.9-16.9c22.1-23.1,7.8-47.4-17.4-49c-42-2.6-65.8-47.6-31.5-84.9
c37.3-34.3,82.3-10.4,84.9,31.6c1.6,25.2,25.8,39.4,48.9,17.3l15.3-15.3l41.2-41.2c0.1-0.1,0.1-0.1,0.2-0.2l0.6-0.6
C488.025,255.656,488.025,228.556,471.325,211.856z"/>
</svg>
)
case 'books':
return (
<svg viewBox="0 0 296.999 296.999" fill="grey">
<g>
<path d="M45.432,35.049c-0.008,0-0.017,0-0.025,0c-2.809,0-5.451,1.095-7.446,3.085c-2.017,2.012-3.128,4.691-3.128,7.543
v159.365c0,5.844,4.773,10.61,10.641,10.625c24.738,0.059,66.184,5.215,94.776,35.136V84.023c0-1.981-0.506-3.842-1.461-5.382
C115.322,40.849,70.226,35.107,45.432,35.049z"/>
<path d="M262.167,205.042V45.676c0-2.852-1.111-5.531-3.128-7.543c-1.995-1.99-4.639-3.085-7.445-3.085c-0.009,0-0.018,0-0.026,0
c-24.793,0.059-69.889,5.801-93.357,43.593c-0.955,1.54-1.46,3.401-1.46,5.382v166.779
c28.592-29.921,70.038-35.077,94.776-35.136C257.394,215.651,262.167,210.885,262.167,205.042z"/>
<path d="M286.373,71.801h-7.706v133.241c0,14.921-12.157,27.088-27.101,27.125c-20.983,0.05-55.581,4.153-80.084,27.344
c42.378-10.376,87.052-3.631,112.512,2.171c3.179,0.724,6.464-0.024,9.011-2.054c2.538-2.025,3.994-5.052,3.994-8.301V82.427
C297,76.568,292.232,71.801,286.373,71.801z"/>
<path d="M18.332,205.042V71.801h-7.706C4.768,71.801,0,76.568,0,82.427v168.897c0,3.25,1.456,6.276,3.994,8.301
c2.545,2.029,5.827,2.78,9.011,2.054c25.46-5.803,70.135-12.547,112.511-2.171c-24.502-23.19-59.1-27.292-80.083-27.342
C30.49,232.13,18.332,219.963,18.332,205.042z"/>
</g>
</svg>
)
case 'archive':
return (
<svg viewBox="0 0 390 390" fill="grey">
<g>
<path d="M182.681,205.334c0,5.21,4.227,9.436,9.436,9.436h5.765c5.21,0,9.436-4.226,9.436-9.436c0-5.211-4.226-9.438-9.436-9.438
h-5.765C186.908,195.897,182.681,200.123,182.681,205.334z"/>
<path d="M383.889,126.058c-4.478-5.191-10.868-8.314-17.674-8.686V64.899c0-25.562-20.797-46.359-46.361-46.359h-75.278
c-25.052,0-38.351,10.578-48.062,18.303c-7.807,6.208-12.518,9.955-22.626,9.955H65.099c-22.78,0-41.313,18.062-41.313,40.264
v30.311c-6.806,0.371-13.196,3.494-17.674,8.686c-4.78,5.541-6.912,12.888-5.839,20.125l30.194,203.803
c1.828,12.338,12.417,21.475,24.89,21.475h279.286c12.473,0,23.063-9.137,24.891-21.475l30.195-203.802
C390.801,138.945,388.669,131.599,383.889,126.058z M161.833,320.412v-10.428c0-2.399,1.945-4.345,4.345-4.345h32.443
c2.399,0,4.345,1.945,4.345,4.345v10.428c0,2.4-1.945,4.345-4.345,4.345h-32.443C163.778,324.757,161.833,322.813,161.833,320.412z
M195,160.323c4.274,0,7.738-3.467,7.738-7.738v-10.718h6.07c4.615,0,8.445,3.564,8.776,8.167l4.893,67.999
c0.175,2.438-0.671,4.838-2.336,6.626c-1.664,1.79-3.996,2.806-6.441,2.806h-37.401c-2.444,0-4.777-1.016-6.441-2.806
c-1.665-1.788-2.511-4.188-2.336-6.626l4.893-67.999c0.331-4.603,4.161-8.167,8.776-8.167h6.07v10.718
C187.262,156.856,190.726,160.323,195,160.323z M198.621,244.81c2.399,0,4.345,1.945,4.345,4.345v10.429
c0,2.399-1.945,4.345-4.345,4.345h-32.443c-2.4,0-4.345-1.945-4.345-4.345v-10.429c0-2.399,1.945-4.345,4.345-4.345H198.621z
M228.167,350.538c0,2.399-1.945,4.345-4.345,4.345h-32.443c-2.399,0-4.345-1.945-4.345-4.345V340.11
c0-2.4,1.946-4.346,4.345-4.346h32.443c2.399,0,4.345,1.945,4.345,4.346V350.538z M228.167,289.708c0,2.4-1.945,4.345-4.345,4.345
h-32.443c-2.399,0-4.345-1.944-4.345-4.345V279.28c0-2.4,1.946-4.346,4.345-4.346h32.443c2.399,0,4.345,1.945,4.345,4.346V289.708z
M335.919,117.333H54.081V87.062c0-6.239,5.602-9.968,11.019-9.968h108.788c20.687,0,32.219-9.173,41.484-16.542
c8.552-6.803,14.732-11.717,29.204-11.717h75.278c8.858,0,16.066,7.206,16.066,16.064V117.333z"/>
</g>
</svg>
)
case 'disc':
return (
<svg viewBox="0 0 49.652 49.652" fill="grey">
<g>
<circle cx="24.826" cy="24.825" r="3.529"/>
<path d="M42.381,7.271C37.693,2.582,31.458,0,24.826,0C18.195,0,11.96,2.583,7.271,7.271c-9.68,9.68-9.68,25.43,0,35.11
c4.689,4.688,10.923,7.271,17.555,7.271c6.632,0,12.867-2.582,17.555-7.271C52.061,32.701,52.061,16.951,42.381,7.271z
M24.86,45.002l0.039-12.587c-1.967,0.019-3.941-0.719-5.442-2.22c-2.965-2.965-2.964-7.772,0-10.737
c0.022-0.022,0.047-0.04,0.069-0.062l-8.935-8.936c4.059-4.072,9.234-6.027,14.363-5.91l-0.039,12.689
c1.915,0.022,3.82,0.759,5.28,2.219c2.942,2.942,2.96,7.699,0.063,10.668l8.967,8.968C35.166,43.164,29.99,45.119,24.86,45.002z"
/>
</g>
</svg>
)
default:
return (
<svg viewBox="0 0 123.769 123.769" fill="grey">
<g>
<path d="M76.05,1.568l-10.101,9.3c-2.3,2.1-5.8,2.1-8.1,0l-10.2-9.2c-3.1-2.8-8-1.7-9.6,2.1l-8.3,20h64.2l-8.3-20.1
C84.05-0.131,79.149-1.231,76.05,1.568z"/>
<path d="M10.749,42.068c-2.9,1.4-1.8,5.7,1.3,5.7h49.8h49.701c3.199,0,4.199-4.3,1.399-5.7l-12.2-6.3h-77.8L10.749,42.068z"/>
<path d="M0.549,90.168l5.3,28.801c0.5,2.899,3,4.8,5.9,4.8h50.1h50.201c2.899,0,5.399-2,5.899-4.8l5.3-28.801
c0.5-2.8-1-5.6-3.699-6.699c-12.801-5-26.2-7.7-36.801-9.301c-2.699-0.399-5.3,1.101-6.3,3.5l-10.1,22.9c-1.8,4-7.5,4-9.201-0.1
l-9.8-22.7c-1.1-2.5-3.7-4-6.4-3.601c-10.6,1.5-24,4.301-36.7,9.301C1.549,84.469-0.051,87.269,0.549,90.168z"/>
</g>
</svg>
)
}
};
export {contentIcon}
export default (props) => {
const torrent = props.torrent;
return (
<div>
<ListItem
onClick={(e) => {
const link = '/torrent/' + torrent.hash;
if(e.button === 1)
return false;
if(e.ctrlKey && e.button === 0) {
let win = window.open(link, '_blank');
//win.focus();
return true;
}
window.router(link)
}}
primaryText={
<a href={'/torrent/' + torrent.hash} ref={(node) => {
if(node)
node.onclick = () => { return false }
}}>
<span className='break-word' style={{
color: torrent.contentCategory != 'xxx' ? 'black' : 'grey'
}}>
{torrent.name}
</span>
</a>
}
secondaryText={
<a href={'/torrent/' + torrent.hash} ref={(node) => {
if(node)
node.onclick = () => { return false }
}}>
<div className='column' style={{height: 'auto', whiteSpace: 'normal', paddingTop: '0.30em'}}>
<div>
{
formatBytes(torrent.size, 1) + ' (' + torrent.files + ' files)'
}
</div>
{
torrent.path && torrent.path.length > 0
?
torrent.path.map((path, index) => {
return <div key={index} className='break-word fs0-75' style={{paddingTop: '0.3em', marginLeft: '0.6em'}}>{path}</div>
})
:
null
}
{
torrent.seeders || torrent.leechers || torrent.completed
?
<div className='break-word fs0-85' style={{paddingTop: '0.35em'}}>
<span style={{color: (torrent.seeders > 0 ? '#00C853' : 'grey')}}>{torrent.seeders} seeders</span>
<span style={{color: (torrent.leechers > 0 ? '#AA00FF' : 'grey'), marginLeft: '12px'}}>{torrent.leechers} leechers</span>
<span style={{color: (torrent.completed > 0 ? '#FF6D00' : 'grey'), marginLeft: '12px'}}>{torrent.completed} completed</span>
</div>
:
null
}
</div>
</a>
}
leftIcon={contentIcon(torrent.contentType, torrent.contentCategory)}
rightIcon={
<a href={`magnet:?xt=urn:btih:${torrent.hash}`}>
<svg style={{
height: '24px',
fill: torrent.contentCategory != 'xxx' ? 'black' : 'grey'
}} onClick={(e) => {
e.preventDefault();
e.stopPropagation();
var win = window.open(`magnet:?xt=urn:btih:${torrent.hash}`, '_self');
}} viewBox="0 0 24 24">
<path d="M15.82 10.736l-5.451 6.717c-.561.691-1.214 1.042-1.94 1.042-1.144
0-2.327-.899-2.753-2.091-.214-.6-.386-1.76.865-2.784 3.417-2.794 6.716-5.446
6.716-5.446l-3.363-4.174s-4.532 3.657-6.771 5.487c-2.581 2.108-3.123 4.468-3.123
6.075 0 4.416 4.014 8.438 8.42 8.438 1.604 0 3.963-.543 6.084-3.128 1.835-2.237
5.496-6.773 5.496-6.773l-4.18-3.363zm-2.604 9.079c-1.353 1.647-3.01 2.519-4.796
2.519-3.471 0-6.753-3.291-6.753-6.771 0-1.789.867-3.443 2.51-4.785 1.206-.986
2.885-2.348 4.18-3.398l1.247 1.599c-1.074.87-2.507 2.033-4.118 3.352-1.471
1.202-1.987 2.935-1.38 4.634.661 1.853 2.479 3.197 4.322 3.197h.001c.86 0
2.122-.288 3.233-1.658l3.355-4.134 1.572 1.294c-1.044 1.291-2.392 2.954-3.373
4.151zm6.152-7.934l4.318-2.88-1.575-.638 1.889-2.414-4.421 2.788 1.716.695-1.927
2.449zm-7.292-7.186l4.916-1.667-1.356-1.022 2.448-2.006-4.991 1.712
1.478 1.114-2.495 1.869z"/></svg>
</a>
}
/>
<Divider />
</div>
)
}

95
src/app/touch.js Normal file
View File

@ -0,0 +1,95 @@
import ReactDOM from 'react-dom';
let listenSwipe = (component, handlers) =>
{
let element = ReactDOM.findDOMNode(component),
startX,
startY,
distanceX,
distanceY,
threshold = 110,
allowedTime = 280,
thresholdAlternativeAxis = 145,
elapsedTime,
startTime;
let touchFunctions = {
touchstart : (e) => {
let touchObject = e.changedTouches[0]
distanceX = 0;
distanceY = 0;
startX = touchObject.pageX
startY = touchObject.pageY
startTime = new Date().getTime()
if(handlers && handlers.preventDefault)
e.preventDefault();
if(handlers && handlers.initSwipe)
handlers.initSwipe.call(component);
},
touchmove : (e) => {
if(handlers && handlers.preventDefault)
e.preventDefault();
},
touchend : (e) => {
let touchObject = e.changedTouches[0];
distanceX = touchObject.pageX - startX;
distanceY = touchObject.pageY - startY;
elapsedTime = new Date().getTime() - startTime; // get time elapsed
let params = {
startX,
startY,
endX: touchObject.pageX,
endY: touchObject.pageY,
distanceX,
distanceY
};
if (elapsedTime <= allowedTime)
{
if(distanceX >= threshold && Math.abs(distanceY) <= thresholdAlternativeAxis)
{
if(handlers && handlers.left)
handlers.left.call(component, params);
} else
if(-distanceX >= threshold && Math.abs(distanceY) <= thresholdAlternativeAxis) {
if(handlers && handlers.right)
handlers.right.call(component, params);
} else
if(distanceY >= threshold && Math.abs(distanceX) <= thresholdAlternativeAxis) {
if(handlers && handlers.top)
handlers.top.call(component, params);
} else
if(-distanceY >= threshold && Math.abs(distanceX) <= thresholdAlternativeAxis) {
if(handlers && handlers.bottom)
handlers.bottom.call(component, params);
}
}
if(handlers && handlers.preventDefault)
e.preventDefault();
}
};
element.addEventListener('touchstart', touchFunctions.touchstart, false);
element.addEventListener('touchmove', touchFunctions.touchmove, false);
element.addEventListener('touchend', touchFunctions.touchend, false);
return touchFunctions;
}
let removeSwipeListener = (component, touchFunctions) => {
let element = ReactDOM.findDOMNode(component);
element.removeEventListener('touchstart', touchFunctions.touchstart);
element.removeEventListener('touchmove', touchFunctions.touchmove);
element.removeEventListener('touchend', touchFunctions.touchend);
}
export { listenSwipe, removeSwipeListener }