feat(albums): init albums module of torrent albums client side

This commit is contained in:
OldHawk
2018-05-22 02:36:27 +08:00
parent daeaea38c2
commit fc3a328e54
12 changed files with 651 additions and 1 deletions

View File

@@ -664,9 +664,11 @@ module.exports = {
AdminTorrentSetSaleType: {name: 'AdminTorrentSetSaleType', enable: true},
AdminTorrentSetRecommendLevel: {name: 'AdminTorrentSetRecommendLevel', enable: true},
AdminCollectionSetRecommendLevel: {name: 'AdminCollectionSetRecommendLevel', enable: true},
AdminAlbumSetRecommendLevel: {name: 'AdminAlbumSetRecommendLevel', enable: true},
AdminTorrentSetReviewedStatus: {name: 'AdminTorrentSetReviewedStatus', enable: true},
OperCreateMakerGroup: {name: 'OperCreateMakerGroup', enable: true},
OperCreateCollection: {name: 'OperCreateCollection', enable: true},
OperCreateAlbum: {name: 'OperCreateAlbum', enable: true},
OperDeleteBackupFiles: {name: 'OperDeleteBackupFiles', enable: true},
userInvitationExchange: {name: 'userInvitationExchange', enable: true},

View File

@@ -0,0 +1,7 @@
(function (app) {
'use strict';
app.registerModule('albums', ['core']);// The core module is required for special route handling; see /core/client/config/core.client.routes
app.registerModule('albums.services');
app.registerModule('albums.routes', ['ui.router', 'core.routes', 'albums.services']);
}(ApplicationConfiguration));

View File

@@ -0,0 +1,37 @@
(function () {
'use strict';
angular
.module('albums.routes')
.config(routeConfig);
routeConfig.$inject = ['$stateProvider'];
function routeConfig($stateProvider) {
$stateProvider
// this menu item defined in torrents menu config
.state('albums', {
url: '/albums',
abstract: true,
template: '<ui-view/>',
data: {
roles: ['user', 'oper', 'admin']
}
})
.state('albums.list', {
url: '',
templateUrl: '/modules/albums/client/views/albums.client.view.html',
data: {
pageTitle: 'PAGETITLE.ALBUMS'
}
})
.state('albums.view', {
url: '/:collectionId',
templateUrl: '/modules/albums/client/views/albums-view.client.view.html',
data: {
pageTitle: 'PAGETITLE.ALBUMS'
}
});
}
}());

View File

@@ -0,0 +1,238 @@
(function () {
'use strict';
angular
.module('albums')
.controller('AlbumItemController', AlbumItemController);
AlbumItemController.$inject = ['$scope', '$state', '$translate', 'MeanTorrentConfig', 'CollectionsService', 'NotifycationService', 'DownloadService',
'DebugConsoleService', 'TorrentGetInfoServices', 'Authentication', 'ResourcesTagsServices', 'ModalConfirmService', 'localStorageService',
'$compile', 'marked', '$window'];
function AlbumItemController($scope, $state, $translate, MeanTorrentConfig, CollectionsService, NotifycationService, DownloadService,
mtDebug, TorrentGetInfoServices, Authentication, ResourcesTagsServices, ModalConfirmService, localStorageService,
$compile, marked, $window) {
var vm = this;
vm.DLS = DownloadService;
vm.TGI = TorrentGetInfoServices;
vm.user = Authentication.user;
vm.RTS = ResourcesTagsServices;
vm.tmdbConfig = MeanTorrentConfig.meanTorrentConfig.tmdbConfig;
vm.torrentRLevels = MeanTorrentConfig.meanTorrentConfig.torrentRecommendLevel;
vm.inputLengthConfig = MeanTorrentConfig.meanTorrentConfig.inputLength;
vm.searchTags = [];
vm.release = [];
/**
* getCollection
*/
vm.getCollection = function () {
CollectionsService.get({
collectionId: $state.params.collectionId
}, function (data) {
vm.collection = data;
$('.backdrop').css('backgroundImage', 'url("' + vm.tmdbConfig.backdropImgBaseUrl + vm.collection.backdrop_path + '")');
//count ave vote
var total = 0;
var count = 0;
var total_users = 0;
angular.forEach(vm.collection.torrents, function (t) {
total += t.resource_detail_info.vote_average;
count += 1;
total_users += t.resource_detail_info.vote_count;
vm.release.push(parseInt(t.resource_detail_info.release_date, 10));
});
vm.collection.vote_count = total_users;
vm.collection.vote_average = Math.floor((total / count) * 10) / 10;
mtDebug.info(vm.collection);
mtDebug.info(vm.release);
});
};
/**
* getMinMaxRelease
* @param c
* @returns {{min: *, max: *}}
*/
vm.getMinMaxRelease = function () {
return {
min: vm.release.length > 0 ? Math.min.apply(null, vm.release) : '',
max: vm.release.length > 0 ? Math.max.apply(null, vm.release) : ''
};
};
/**
* getCollectionOverviewContent
* @param m
* @returns {*}
*/
vm.getCollectionOverviewContent = function (c) {
return c ? marked(c.overview, {sanitize: true}) : '';
};
/**
* beginRemoveCollection
* @param m
*/
vm.beginRemoveCollection = function (c) {
var modalOptions = {
closeButtonText: $translate.instant('ABOUT.DELETE_CONFIRM_CANCEL'),
actionButtonText: $translate.instant('ABOUT.DELETE_CONFIRM_OK'),
headerText: $translate.instant('ABOUT.DELETE_CONFIRM_HEADER_TEXT'),
bodyText: $translate.instant('COLLECTIONS.DELETE_CONFIRM_BODY_TEXT')
};
ModalConfirmService.showModal({}, modalOptions)
.then(function (result) {
c.$remove(function (res) {
NotifycationService.showSuccessNotify('COLLECTIONS.DELETE_SUCCESSFULLY');
$state.go('collections.list');
}, function (res) {
NotifycationService.showErrorNotify(res.data.message, 'COLLECTIONS.DELETE_FAILED');
});
});
};
/**
* beginEditCollectionOverview
* @param m
*/
vm.beginEditCollectionOverview = function (c) {
var el = $('#' + c._id);
el.markdown({
autofocus: true,
savable: true,
hideable: true,
iconlibrary: 'fa',
resize: 'vertical',
language: localStorageService.get('storage_user_lang'),
fullscreen: {enable: false},
onSave: function (e) {
if (e.isDirty()) {
vm.collection.overview = e.getContent();
vm.collection.$update(function (res) {
vm.collection = res;
NotifycationService.showSuccessNotify('COLLECTIONS.EDIT_OVERVIEW_SUCCESSFULLY');
}, function (res) {
NotifycationService.showErrorNotify(res.data.message, 'COLLECTIONS.EDIT_OVERVIEW_FAILED');
});
e.$options.hideable = true;
e.blur();
} else {
e.$options.hideable = true;
e.blur();
}
},
onChange: function (e) {
e.$options.hideable = false;
},
onShow: function (e) {
$('#' + e.$editor.attr('id') + ' .md-input').textcomplete([
{ // emoji strategy
match: /\B:([\-+\w]*)$/,
search: function (term, callback) {
callback($.map(window.emojies, function (emoji) {
return emoji.indexOf(term) === 0 ? emoji : null;
}));
},
template: function (value) {
return '<img class="ac-emoji" src="/graphics/emojis/' + value + '.png" />' + '<span class="ac-emoji-text">' + value + '</span>';
},
replace: function (value) {
return ':' + value + ': ';
},
index: 1
}
]);
e.setContent(c.overview);
$('#' + e.$editor.attr('id') + ' .md-input').attr('maxlength', vm.inputLengthConfig.collectionsOverviewLength);
var elei = $('#' + e.$editor.attr('id') + ' .md-input');
angular.element(elei).css('height', '200px');
angular.element(elei).css('color', '#333');
var inputInfo = angular.element('<span></span>');
inputInfo.addClass('pull-right');
inputInfo.addClass('input-length');
inputInfo.text(e.getContent().length + '/' + vm.inputLengthConfig.collectionsOverviewLength);
$('#' + e.$editor.attr('id') + ' .md-header').append(inputInfo);
$('#' + e.$editor.attr('id') + ' .md-input').on('input propertychange', function (evt) {
inputInfo.text(e.getContent().length + '/' + vm.inputLengthConfig.collectionsOverviewLength);
});
var ele = $('#' + e.$editor.attr('id') + ' .md-footer');
angular.element(ele).addClass('text-right');
angular.element(ele[0].childNodes[0]).addClass('btn-width-80');
ele[0].childNodes[0].innerText = $translate.instant('FORUMS.BTN_SAVE');
var cbtn = angular.element('<button class="btn btn-default btn-width-80 margin-left-10">' + $translate.instant('FORUMS.BTN_CANCEL') + '</button>');
cbtn.bind('click', function (evt) {
e.setContent(c.overview);
e.$options.hideable = true;
e.blur();
});
ele.append(cbtn);
$compile(e.$editor.contents())($scope);
},
onPreview: function (e) {
$('#' + e.$editor.attr('id') + ' .md-footer').css('display', 'none');
},
onPreviewEnd: function (e) {
$('#' + e.$editor.attr('id') + ' .md-footer').css('display', 'block');
}
});
};
/**
* vm.setRecommendLevel
*/
vm.setRecommendLevel = function (item, rl) {
CollectionsService.setRecommendLevel({
_id: item._id,
rlevel: rl.value
}, function (res) {
vm.collection = res;
NotifycationService.showSuccessNotify('COLLECTIONS.SETRLEVEL_SUCCESSFULLY');
}, function (res) {
NotifycationService.showSuccessNotify('COLLECTIONS.SETRLEVEL_ERROR');
});
};
/**
* removeFromCollections
* @param item
*/
vm.removeFromCollections = function (item) {
var modalOptions = {
closeButtonText: $translate.instant('COLLECTIONS.REMOVE_CONFIRM_CANCEL'),
actionButtonText: $translate.instant('COLLECTIONS.REMOVE_CONFIRM_OK'),
headerText: $translate.instant('COLLECTIONS.REMOVE_CONFIRM_HEADER_TEXT'),
bodyText: $translate.instant('COLLECTIONS.REMOVE_CONFIRM_BODY_TEXT')
};
ModalConfirmService.showModal({}, modalOptions)
.then(function (result) {
CollectionsService.removeFromCollection({
collectionId: vm.collection._id,
torrentId: item._id
}, function (res) {
mtDebug.info(res);
vm.collection = res;
NotifycationService.showSuccessNotify('COLLECTIONS.REMOVE_SUCCESSFULLY');
}, function (res) {
NotifycationService.showErrorNotify(res.data.message, 'COLLECTIONS.REMOVE_FAILED');
});
});
};
}
}());

View File

@@ -0,0 +1,106 @@
(function () {
'use strict';
angular
.module('albums')
.controller('AlbumController', AlbumController);
AlbumController.$inject = ['$scope', '$translate', 'getStorageLangService', 'MeanTorrentConfig', 'CollectionsService', 'NotifycationService',
'DebugConsoleService'];
function AlbumController($scope, $translate, getStorageLangService, MeanTorrentConfig, CollectionsService, NotifycationService,
mtDebug) {
var vm = this;
vm.lang = getStorageLangService.getLang();
vm.appConfig = MeanTorrentConfig.meanTorrentConfig.app;
vm.itemsPerPageConfig = MeanTorrentConfig.meanTorrentConfig.itemsPerPage;
vm.tmdbConfig = MeanTorrentConfig.meanTorrentConfig.tmdbConfig;
/**
* buildPager
*/
vm.buildPager = function () {
vm.pagedItems = [];
vm.itemsPerPage = vm.itemsPerPageConfig.collectionsListPerPage;
vm.currentPage = 1;
vm.figureOutItemsToDisplay();
};
/**
* figureOutItemsToDisplay
* @param callback
*/
vm.figureOutItemsToDisplay = function (callback) {
vm.getCollectionsList(vm.currentPage, function (items) {
vm.filterLength = items.total;
vm.pagedItems = items.rows;
if (callback) callback();
});
};
/**
* getCollectionsList
* @param p
* @param callback
*/
vm.getCollectionsList = function (p, callback) {
CollectionsService.get({
skip: (p - 1) * vm.itemsPerPage,
limit: vm.itemsPerPage,
keys: vm.search
}, function (data) {
mtDebug.info(data);
callback(data);
}, function (err) {
NotifycationService.showErrorNotify(err.data.message, 'COLLECTIONS.LIST_ERROR');
});
};
/**
* getVoteAverage
* @param c
* @returns {number}
*/
vm.getVoteAverage = function (c) {
var total = 0;
var count = 0;
var avg = 0;
angular.forEach(c.torrents, function (t) {
total += t.resource_detail_info.vote_average;
count += 1;
});
avg = Math.floor((total / count) * 10) / 10;
return avg || 0;
};
/**
* getMinMaxRelease
* @param c
* @returns {{min: *, max: *}}
*/
vm.getMinMaxRelease = function (c) {
var re = [];
angular.forEach(c.torrents, function (t) {
re.push(parseInt(t.resource_detail_info.release_date, 10));
});
return {
min: re.length > 0 ? Math.min.apply(null, re) : '',
max: re.length > 0 ? Math.max.apply(null, re) : ''
};
};
/**
* pageChanged
*/
vm.pageChanged = function () {
vm.figureOutItemsToDisplay();
};
}
}());

View File

@@ -0,0 +1,78 @@
(function () {
'use strict';
// Users service used for communicating with the users REST endpoint
angular
.module('albums.services')
.factory('AlbumsService', AlbumsService);
AlbumsService.$inject = ['$resource', 'CacheFactory'];
function AlbumsService($resource, CacheFactory) {
var albumsCache = CacheFactory.get('albumsCache') || CacheFactory.createCache('albumsCache');
var removeCache = function (res) {
albumsCache.removeAll();
return res.resource;
};
var album = $resource('/api/albums/:albumId', {
albumId: '@_id'
}, {
get: {
method: 'GET',
cache: albumsCache
},
query: {
method: 'GET',
isArray: true,
cache: albumsCache
},
update: {
method: 'PUT',
interceptor: {response: removeCache}
},
save: {
method: 'POST',
interceptor: {response: removeCache}
},
remove: {
method: 'DELETE',
interceptor: {response: removeCache}
},
delete: {
method: 'DELETE',
interceptor: {response: removeCache}
},
insertIntoAlbum: {
method: 'PUT',
url: '/api/albums/:albumId/insert/:torrentId',
params: {
albumId: '@albumId',
torrentId: '@torrentId'
},
interceptor: {response: removeCache}
},
removeFromAlbum: {
method: 'PUT',
url: '/api/albums/:albumId/remove/:torrentId',
params: {
albumId: '@albumId',
torrentId: '@torrentId'
},
interceptor: {response: removeCache}
},
setRecommendLevel: {
method: 'PUT',
url: '/api/albums/:albumId/set/recommendlevel/:rlevel',
params: {
albumId: '@_id',
rlevel: '@rlevel'
},
interceptor: {response: removeCache}
}
});
return album;
}
}());

View File

@@ -0,0 +1,104 @@
<section ng-controller="CollectionItemController as vm" ng-init="vm.getCollection();">
<div class="backdrop attachment-scroll">
<div class="filter">
<div class="container collection-view-top">
<div class="row margin-bottom-10">
<div class="col-sm-9">
<span class="item-name">
<h3 class="margin-bottom-10">
{{vm.collection.name}}
</h3>
</span>
</div>
<div class="col-sm-3 text-right">
<span class="item-release">
<h3 class="margin-bottom-10">
<span>{{vm.getMinMaxRelease().min}}</span><span ng-if="vm.release.length>1">-{{vm.getMinMaxRelease().max}}</span>
</h3>
</span>
</div>
<div class="col-sm-12">
<ul class="list-unstyled item-data list-inline">
<li><i class="fa fa-list text-info"> {{'COLLECTIONS.FILES_NUMBERS' | translate}}: {{vm.collection.torrents.length}}</i>
</li>
<li><i class="fa fa-star-half-o text-info"> {{'COLLECTIONS.VOTE_AVERAGE' | translate}}:
{{vm.collection.vote_average}} / {{vm.collection.vote_count}} {{ 'TMDB_FIELDS.VOTE_UNIT' | translate}}
</i></li>
</ul>
</div>
</div>
<div class="row margin-bottom-20">
<div class="col-sm-12">
<div>
<div class="item-overview" id="{{vm.collection._id}}" data-provide="markdown"
ng-bind-html="vm.getCollectionOverviewContent(vm.collection)">
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-sm-12">
<div class="collection-button text-right">
<button class="btn btn-width-160 margin-top-5" mouse-enter-toggle-class="btn-default" base-class="btn-mt-o"
ng-if="vm.user.isOper" ng-click="vm.beginEditCollectionOverview(vm.collection)">
{{'COLLECTIONS.BTN_EDIT_OVERVIEW' | translate}}
</button>
<button class="btn btn-width-160 margin-top-5" mouse-enter-toggle-class="btn-default" base-class="btn-mt-o"
ng-if="vm.user.isOper" ng-click="vm.beginRemoveCollection(vm.collection)">
{{'COLLECTIONS.BTN_REMOVE_COLLECTION' | translate}}
</button>
<div class="btn-group width-160 margin-top-5" ng-if="vm.user.isOper" uib-dropdown dropdown-append-to-body>
<button id="btn-append-to-body" type="button"
class="btn btn-block"
mouse-enter-toggle-class="btn-default" base-class="btn-mt-o"
ng-click="$event.stopPropagation();"
uib-dropdown-toggle>
{{'ADMIN_BASIC_RLEVEL_SET' | translate }} <span class="caret"></span>
</button>
<ul class="dropdown-menu-left" uib-dropdown-menu role="menu"
aria-labelledby="btn-append-to-body">
<li role="menuitem" ng-repeat="l in vm.torrentRLevels.value"
ng-class="{'active': vm.collection.recommend_level == l.value}">
<a href="#"
ng-click="vm.setRecommendLevel(vm.collection, l);">{{ 'TORRENT_RECOMMEND_LEVEL_ITEM.' + l.name | translate}}</a>
</li>
</ul>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="container">
<div class="row margin-top-20 margin-bottom-20">
<div class="col-md-12 torrent-list" id="top_of_torrent_list" ng-show="vm.collection.torrents">
<div class="table-responsive">
<table class="table table-hover tb-v-middle">
<thead>
<tr>
<th class="td-width-0"></th>
<th>{{ 'TABLE_FIELDS.INFO' | translate}}</th>
<th>{{ 'TABLE_FIELDS.VOTES' | translate}}</th>
<th class="text-center">{{ 'TABLE_FIELDS.LIFETIME' | translate}}</th>
<th class="text-center">{{ 'TABLE_FIELDS.SIZE' | translate}}</th>
<th>
<abbr title="{{ 'TABLE_FIELDS.ABBR_SEEDS_LEECHERS_FINISHED' | translate}}" class="initialism">
{{ 'TABLE_FIELDS.SEEDS_LEECHERS_FINISHED' | translate}}
</abbr>
</th>
<th class="text-center">{{ 'TABLE_FIELDS.PUBLISHER' | translate}}</th>
</tr>
</thead>
<tbody torrent-list-item parent="vm" item="item" list="vm.collection.torrents" ng-repeat="item in vm.collection.torrents"></tbody>
</table>
</div>
</div>
</div>
</div>
</section>

View File

@@ -0,0 +1,75 @@
<section class="container" ng-controller="AlbumController as vm" ng-init="vm.buildPager();">
<div class="row margin-top-20">
<div class="col-sm-12">
<span id="top_of_collections_list">
<h3 class="margin-bottom-30">
{{'COLLECTIONS.COLL_LIST' | translate}}
</h3>
</span>
<div class="collections-list">
<div class="pagination-div-top">
<div class="row">
<div class="col-sm-12 col-md-9">
<ul uib-pagination boundary-links="true" max-size="8" items-per-page="vm.itemsPerPage" total-items="vm.filterLength"
ng-model="vm.currentPage"
ng-change="vm.pageChanged()"
first-text="{{ 'PAGE_TEXT_FIRST' | translate}}" previous-text="{{ 'PAGE_TEXT_PREVIOUS' | translate}}"
next-text="{{ 'PAGE_TEXT_NEXT' | translate}}" last-text="{{ 'PAGE_TEXT_LAST' | translate}}">
</ul>
</div>
<div class="col-sm-12 col-md-3">
<input class="form-control margin-top-20 margin-bottom-20" type="text" ng-model="vm.search" placeholder="Search"
ng-change="vm.figureOutItemsToDisplay()"/>
</div>
</div>
</div>
<div class="row">
<div class="collection-items">
<div ng-repeat="m in vm.pagedItems">
<div data-ng-if="$index != 0 && $index % 2 == 0" class="clearfix visible-sm-block"></div>
<div data-ng-if="$index != 0 && $index % 3 == 0" class="clearfix visible-md-block visible-lg-block"></div>
<div class="col-sm-6 col-md-4">
<a ui-sref="collections.view({ collectionId: m._id })">
<div class="collection-item">
<img ng-src="{{vm.tmdbConfig.backdropImgBaseUrl_780 + m.backdrop_path}}">
<div class="item-info text-center">
<div class="name">{{m.name}}</div>
<div class="row margin-top-40 collection-data">
<div class="item-release margin-bottom-10">
<span>{{vm.getMinMaxRelease(m).min}}</span><span
ng-if="m.torrents.length>1">-{{vm.getMinMaxRelease(m).max}}</span>
</div>
<div class="col-xs-6 col-small-padding text-center">
<i class="fa fa-list text-info">
<strong> {{'COLLECTIONS.FILES_NUMBERS' | translate}}: {{m.torrents.length}}</strong></i>
</div>
<div class="col-xs-6 col-small-padding text-left">
<i class="fa fa-star-half-o text-info">
<strong> {{'COLLECTIONS.VOTE_AVERAGE' | translate}}: {{vm.getVoteAverage(m);}}</strong></i>
</div>
</div>
</div>
</div>
</a>
</div>
</div>
</div>
</div>
<div class="pagination-div-bottom">
<ul uib-pagination boundary-links="true" max-size="8" items-per-page="vm.itemsPerPage" total-items="vm.filterLength"
ng-model="vm.currentPage"
ng-change="vm.pageChanged()"
first-text="{{ 'PAGE_TEXT_FIRST' | translate}}" previous-text="{{ 'PAGE_TEXT_PREVIOUS' | translate}}"
next-text="{{ 'PAGE_TEXT_NEXT' | translate}}" last-text="{{ 'PAGE_TEXT_LAST' | translate}}">
</ul>
</div>
</div>
</div>
</div>
</section>

View File

@@ -239,6 +239,7 @@
MAKER: 'Resources Group',
OPERLIST: 'Admin/Oper List',
COLLECTIONS: 'Movie Collections',
ALBUMS: 'Albums',
VIP: 'Vip',
VIP_DONATE: 'Vip Donate',
VIP_RULES: 'Vip Rules',

View File

@@ -239,6 +239,7 @@
MAKER: '資源小組',
OPERLIST: 'Admin/Oper 列表',
COLLECTIONS: '電影系列',
ALBUMS: '資源專輯',
VIP: 'Vip',
VIP_DONATE: '捐贈VIP',
VIP_RULES: 'Vip使用者協議',

View File

@@ -239,6 +239,7 @@
MAKER: '资源小组',
OPERLIST: 'Admin/Oper 列表',
COLLECTIONS: '电影系列',
ALBUMS: '资源专辑',
VIP: 'Vip',
VIP_DONATE: '捐赠VIP',
VIP_RULES: 'Vip用户协议',

View File

@@ -421,7 +421,7 @@
.home-search {
.search-title {
color: #fff;
font-size: 2em;
font-size: 24px;
font-weight: 400;
margin-bottom: 0 !important;
}