1
0
Fork 0
mirror of https://github.com/jellyfin/jellyfin-web synced 2025-03-30 19:56:21 +00:00

Merge remote-tracking branch 'upstream/master'

This commit is contained in:
Tim Hobbs 2014-03-30 16:28:19 -07:00
commit 862d03a684
11 changed files with 196 additions and 62 deletions

View file

@ -4058,7 +4058,7 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout, wi
options.ForceTitle = true; options.ForceTitle = true;
} }
var url = self.getUrl("Packages/" + packageId + "Reviews", options); var url = self.getUrl("Packages/" + packageId + "/Reviews", options);
return self.ajax({ return self.ajax({
type: "GET", type: "GET",

View file

@ -7,8 +7,14 @@
background-size: 100%; background-size: 100%;
vertical-align: middle; vertical-align: middle;
margin: 0 .6em; margin: 0 .6em;
cursor: pointer;
border: 0 !important;
} }
.btnCast:hover {
opacity: .5;
}
.btnDefaultCast { .btnDefaultCast {
background-image: url(images/chromecast/ic_media_route_off_holo_dark.png); background-image: url(images/chromecast/ic_media_route_off_holo_dark.png);
} }

View file

@ -1199,3 +1199,21 @@ a.itemTag:hover {
width: 32.95%; width: 32.95%;
} }
} }
.playerSelectionPanel {
width: 250px;
}
.ui-panel-dismiss-open.ui-panel-dismiss-position-right[data-panelid=playerSelectionPanel] {
right: 250px;
}
@media all and (min-width: 600px) {
.playerSelectionPanel {
width: 300px;
}
.ui-panel-dismiss-open.ui-panel-dismiss-position-right[data-panelid=playerSelectionPanel] {
right: 300px;
}
}

View file

@ -127,6 +127,8 @@
</div> </div>
<div class="profileTab tabTranscodingProfiles"> <div class="profileTab tabTranscodingProfiles">
<p>Add transcoding profiles to indicate which formats should be used when transcoding is required.</p> <p>Add transcoding profiles to indicate which formats should be used when transcoding is required.</p>
<button class="btnAddTranscodingProfile" type="button" data-mini="true" data-icon="plus">New</button>
<br />
<div class="transcodingProfiles"></div> <div class="transcodingProfiles"></div>
</div> </div>
<div class="profileTab tabContainerProfiles"> <div class="profileTab tabContainerProfiles">
@ -176,7 +178,7 @@
</div> </div>
<div style="margin: 1em 0;"> <div style="margin: 1em 0;">
<label for="txtDirectPlayContainer">Containers:</label> <label for="txtDirectPlayContainer">Container:</label>
<input type="text" id="txtDirectPlayContainer" data-mini="true" /> <input type="text" id="txtDirectPlayContainer" data-mini="true" />
<div>Separated by comma. This can be left empty to apply to all containers</div> <div>Separated by comma. This can be left empty to apply to all containers</div>
</div> </div>
@ -204,10 +206,56 @@
</form> </form>
</div> </div>
</div>
<div data-role="popup" data-transition="slidefade" id="transcodingProfilePopup" class="popup">
<div class="ui-bar-a" style="text-align: center; padding: 0 20px;">
<h3>Transcoding Profile</h3>
</div>
<div data-role="content">
<form class="transcodingProfileForm" style="min-width: 250px;">
<div style="margin: 1em 0;">
<label for="selectTranscodingProfileType">Type:</label>
<select id="selectTranscodingProfileType" data-mini="true">
<option value="Audio">Audio</option>
<option value="Photo">Photo</option>
<option value="Video">Video</option>
</select>
</div>
<div style="margin: 1em 0;">
<label for="txtTranscodingContainer">Containers:</label>
<input type="text" id="txtTranscodingContainer" data-mini="true" required="required" />
</div>
<div id="fldTranscodingVideoCodec" style="margin: 1em 0;">
<label for="txtTranscodingVideoCodec">Video codec:</label>
<input type="text" id="txtTranscodingVideoCodec" data-mini="true" />
</div>
<div id="fldTranscodingAudioCodec" style="margin: 1em 0 2em;">
<label for="txtTranscodingAudioCodec">Audio codec:</label>
<input type="text" id="txtTranscodingAudioCodec" data-mini="true" />
</div>
<p>
<button type="submit" data-theme="b" data-icon="check" data-mini="true">
Ok
</button>
<button type="button" data-icon="delete" onclick="$(this).parents('.popup').popup('close');" data-mini="true">
Cancel
</button>
</p>
</form>
</div>
</div> </div>
<script type="text/javascript"> <script type="text/javascript">
$('.dlnaProfileForm').off('submit', DlnaProfilePage.onSubmit).on('submit', DlnaProfilePage.onSubmit); $('.dlnaProfileForm').off('submit', DlnaProfilePage.onSubmit).on('submit', DlnaProfilePage.onSubmit);
$('.editDirectPlayProfileForm').off('submit', DlnaProfilePage.onDirectPlayFormSubmit).on('submit', DlnaProfilePage.onDirectPlayFormSubmit); $('.editDirectPlayProfileForm').off('submit', DlnaProfilePage.onDirectPlayFormSubmit).on('submit', DlnaProfilePage.onDirectPlayFormSubmit);
$('.transcodingProfileForm').off('submit', DlnaProfilePage.onTranscodingProfileFormSubmit).on('submit', DlnaProfilePage.onTranscodingProfileFormSubmit);
</script> </script>
</div> </div>
</body> </body>

View file

@ -29,21 +29,15 @@
var selectmenu = $('#selectVersion', page).html(html); var selectmenu = $('#selectVersion', page).html(html);
var packageVersion;
if (!installedPlugin) { if (!installedPlugin) {
$('#pCurrentVersion', page).hide().html(""); $('#pCurrentVersion', page).hide().html("");
} }
// If we don't have a package version to select, pick the first release build var packageVersion = packageInfo.versions.filter(function (current) {
if (!packageVersion) {
packageVersion = packageInfo.versions.filter(function (current) { return current.classification == "Release";
})[0];
return current.classification == "Release";
})[0];
}
// If we still don't have a package version to select, pick the first Beta build // If we still don't have a package version to select, pick the first Beta build
if (!packageVersion) { if (!packageVersion) {

View file

@ -1,4 +1,4 @@
(function ($, document, window) { (function($, document, window) {
var currentProfile; var currentProfile;
@ -9,7 +9,7 @@
Dashboard.showLoadingMsg(); Dashboard.showLoadingMsg();
getProfile().done(function (result) { getProfile().done(function(result) {
currentProfile = result; currentProfile = result;
@ -38,7 +38,7 @@
$('#txtName', page).val(profile.Name); $('#txtName', page).val(profile.Name);
$('.chkMediaType', page).each(function () { $('.chkMediaType', page).each(function() {
this.checked = (profile.SupportedMediaTypes || '').split(',').indexOf(this.getAttribute('data-value')) != -1; this.checked = (profile.SupportedMediaTypes || '').split(',').indexOf(this.getAttribute('data-value')) != -1;
}).checkboxradio('refresh'); }).checkboxradio('refresh');
@ -134,9 +134,7 @@
if (profile.Type == 'Video') { if (profile.Type == 'Video') {
html += '<p>Video Codec: ' + (profile.VideoCodec || 'All') + '</p>'; html += '<p>Video Codec: ' + (profile.VideoCodec || 'All') + '</p>';
html += '<p>Audio Codec: ' + (profile.AudioCodec || 'All') + '</p>'; html += '<p>Audio Codec: ' + (profile.AudioCodec || 'All') + '</p>';
} } else if (profile.Type == 'Audio') {
else if (profile.Type == 'Audio') {
html += '<p>Codec: ' + (profile.AudioCodec || 'All') + '</p>'; html += '<p>Codec: ' + (profile.AudioCodec || 'All') + '</p>';
} }
@ -151,13 +149,13 @@
var elem = $('.directPlayProfiles', page).html(html).trigger('create'); var elem = $('.directPlayProfiles', page).html(html).trigger('create');
$('.btnDeleteProfile', elem).on('click', function () { $('.btnDeleteProfile', elem).on('click', function() {
var index = this.getAttribute('data-profileindex'); var index = this.getAttribute('data-profileindex');
deleteDirectPlayProfile(page, index); deleteDirectPlayProfile(page, index);
}); });
$('.lnkEditSubProfile', elem).on('click', function () { $('.lnkEditSubProfile', elem).on('click', function() {
var index = parseInt(this.getAttribute('data-profileindex')); var index = parseInt(this.getAttribute('data-profileindex'));
@ -192,7 +190,7 @@
} }
html += '<li>'; html += '<li>';
html += '<a href="#">'; html += '<a data-profileindex="' + i + '" class="lnkEditSubProfile" href="#">';
html += '<p>Protocol: ' + (profile.Protocol || 'Http') + '</p>'; html += '<p>Protocol: ' + (profile.Protocol || 'Http') + '</p>';
html += '<p>Container: ' + (profile.Container || 'All') + '</p>'; html += '<p>Container: ' + (profile.Container || 'All') + '</p>';
@ -200,9 +198,7 @@
if (profile.Type == 'Video') { if (profile.Type == 'Video') {
html += '<p>Video Codec: ' + (profile.VideoCodec || 'All') + '</p>'; html += '<p>Video Codec: ' + (profile.VideoCodec || 'All') + '</p>';
html += '<p>Audio Codec: ' + (profile.AudioCodec || 'All') + '</p>'; html += '<p>Audio Codec: ' + (profile.AudioCodec || 'All') + '</p>';
} } else if (profile.Type == 'Audio') {
else if (profile.Type == 'Audio') {
html += '<p>Codec: ' + (profile.AudioCodec || 'All') + '</p>'; html += '<p>Codec: ' + (profile.AudioCodec || 'All') + '</p>';
} }
@ -217,11 +213,32 @@
var elem = $('.transcodingProfiles', page).html(html).trigger('create'); var elem = $('.transcodingProfiles', page).html(html).trigger('create');
$('.btnDeleteProfile', elem).on('click', function () { $('.btnDeleteProfile', elem).on('click', function() {
var index = this.getAttribute('data-profileindex'); var index = this.getAttribute('data-profileindex');
deleteTranscodingProfile(page, index); deleteTranscodingProfile(page, index);
}); });
$('.lnkEditSubProfile', elem).on('click', function () {
var index = parseInt(this.getAttribute('data-profileindex'));
editTranscodingProfile(page, currentProfile.TranscodingProfiles[index]);
});
}
function editTranscodingProfile(page, transcodingProfile) {
isSubProfileNew = transcodingProfile == null;
transcodingProfile = transcodingProfile || {};
currentSubProfile = transcodingProfile;
var popup = $('#transcodingProfilePopup', page).popup('open');
$('#selectTranscodingProfileType', popup).val(transcodingProfile.Type || 'Video').selectmenu('refresh').trigger('change');
$('#txtTranscodingContainer', popup).val(transcodingProfile.Container || '');
$('#txtTranscodingAudioCodec', popup).val(transcodingProfile.AudioCodec || '');
$('#txtTranscodingVideoCodec', popup).val(transcodingProfile.VideoCodec || '');
} }
function deleteTranscodingProfile(page, index) { function deleteTranscodingProfile(page, index) {
@ -232,6 +249,25 @@
} }
function saveTranscodingProfile(page) {
currentSubProfile.Type = $('#selectTranscodingProfileType', page).val();
currentSubProfile.Container = $('#txtTranscodingContainer', page).val();
currentSubProfile.AudioCodec = $('#txtTranscodingAudioCodec', page).val();
currentSubProfile.VideoCodec = $('#txtTranscodingVideoCodec', page).val();
if (isSubProfileNew) {
currentProfile.TranscodingProfiles.push(currentSubProfile);
}
renderSubProfiles(page, currentProfile);
currentSubProfile = null;
$('#transcodingProfilePopup', page).popup('close');
}
function renderContainerProfiles(page, profiles) { function renderContainerProfiles(page, profiles) {
var html = ''; var html = '';
@ -251,14 +287,14 @@
} }
html += '<li>'; html += '<li>';
html += '<a href="#">'; html += '<a data-profileindex="' + i + '" class="lnkEditSubProfile" href="#">';
html += '<p>Container: ' + (profile.Container || 'All') + '</p>'; html += '<p>Container: ' + (profile.Container || 'All') + '</p>';
if (profile.Conditions && profile.Conditions.length) { if (profile.Conditions && profile.Conditions.length) {
html += '<p>Conditions: '; html += '<p>Conditions: ';
html += profile.Conditions.map(function (c) { html += profile.Conditions.map(function(c) {
return c.Property; return c.Property;
}).join(', '); }).join(', ');
html += '</p>'; html += '</p>';
@ -275,7 +311,7 @@
var elem = $('.containerProfiles', page).html(html).trigger('create'); var elem = $('.containerProfiles', page).html(html).trigger('create');
$('.btnDeleteProfile', elem).on('click', function () { $('.btnDeleteProfile', elem).on('click', function() {
var index = this.getAttribute('data-profileindex'); var index = this.getAttribute('data-profileindex');
deleteContainerProfile(page, index); deleteContainerProfile(page, index);
@ -311,14 +347,14 @@
} }
html += '<li>'; html += '<li>';
html += '<a href="#">'; html += '<a data-profileindex="' + i + '" class="lnkEditSubProfile" href="#">';
html += '<p>Codec: ' + (profile.Codec || 'All') + '</p>'; html += '<p>Codec: ' + (profile.Codec || 'All') + '</p>';
if (profile.Conditions && profile.Conditions.length) { if (profile.Conditions && profile.Conditions.length) {
html += '<p>Conditions: '; html += '<p>Conditions: ';
html += profile.Conditions.map(function (c) { html += profile.Conditions.map(function(c) {
return c.Property; return c.Property;
}).join(', '); }).join(', ');
html += '</p>'; html += '</p>';
@ -335,7 +371,7 @@
var elem = $('.codecProfiles', page).html(html).trigger('create'); var elem = $('.codecProfiles', page).html(html).trigger('create');
$('.btnDeleteProfile', elem).on('click', function () { $('.btnDeleteProfile', elem).on('click', function() {
var index = this.getAttribute('data-profileindex'); var index = this.getAttribute('data-profileindex');
deleteCodecProfile(page, index); deleteCodecProfile(page, index);
@ -369,23 +405,21 @@
} }
html += '<li>'; html += '<li>';
html += '<a href="#">'; html += '<a data-profileindex="' + i + '" class="lnkEditSubProfile" href="#">';
html += '<p>Container: ' + (profile.Container || 'All') + '</p>'; html += '<p>Container: ' + (profile.Container || 'All') + '</p>';
if (profile.Type == 'Video') { if (profile.Type == 'Video') {
html += '<p>Video Codec: ' + (profile.VideoCodec || 'All') + '</p>'; html += '<p>Video Codec: ' + (profile.VideoCodec || 'All') + '</p>';
html += '<p>Audio Codec: ' + (profile.AudioCodec || 'All') + '</p>'; html += '<p>Audio Codec: ' + (profile.AudioCodec || 'All') + '</p>';
} } else if (profile.Type == 'Audio') {
else if (profile.Type == 'Audio') {
html += '<p>Codec: ' + (profile.AudioCodec || 'All') + '</p>'; html += '<p>Codec: ' + (profile.AudioCodec || 'All') + '</p>';
} }
if (profile.Conditions && profile.Conditions.length) { if (profile.Conditions && profile.Conditions.length) {
html += '<p>Conditions: '; html += '<p>Conditions: ';
html += profile.Conditions.map(function (c) { html += profile.Conditions.map(function(c) {
return c.Property; return c.Property;
}).join(', '); }).join(', ');
html += '</p>'; html += '</p>';
@ -402,7 +436,7 @@
var elem = $('.mediaProfiles', page).html(html).trigger('create'); var elem = $('.mediaProfiles', page).html(html).trigger('create');
$('.btnDeleteProfile', elem).on('click', function () { $('.btnDeleteProfile', elem).on('click', function() {
var index = this.getAttribute('data-profileindex'); var index = this.getAttribute('data-profileindex');
deleteMediaProfile(page, index); deleteMediaProfile(page, index);
@ -430,8 +464,7 @@
url: ApiClient.getUrl("Dlna/Profiles/" + id), url: ApiClient.getUrl("Dlna/Profiles/" + id),
data: JSON.stringify(profile), data: JSON.stringify(profile),
contentType: "application/json" contentType: "application/json"
}).done(function() {
}).done(function () {
Dashboard.alert('Settings saved.'); Dashboard.alert('Settings saved.');
}); });
@ -443,8 +476,7 @@
url: ApiClient.getUrl("Dlna/Profiles"), url: ApiClient.getUrl("Dlna/Profiles"),
data: JSON.stringify(profile), data: JSON.stringify(profile),
contentType: "application/json" contentType: "application/json"
}).done(function() {
}).done(function () {
Dashboard.navigate('dlnaprofiles.html'); Dashboard.navigate('dlnaprofiles.html');
@ -460,7 +492,7 @@
profile.Name = $('#txtName', page).val(); profile.Name = $('#txtName', page).val();
profile.EnableAlbumArtInDidl = $('#chkEnableAlbumArtInDidl', page).checked(); profile.EnableAlbumArtInDidl = $('#chkEnableAlbumArtInDidl', page).checked();
profile.SupportedMediaTypes = $('.chkMediaType:checked', page).get().map(function (c) { profile.SupportedMediaTypes = $('.chkMediaType:checked', page).get().map(function(c) {
return c.getAttribute('data-value'); return c.getAttribute('data-value');
}).join(','); }).join(',');
@ -477,18 +509,18 @@
profile.Identification.DeviceDescription = $('#txtIdDeviceDescription', page).val(); profile.Identification.DeviceDescription = $('#txtIdDeviceDescription', page).val();
} }
$(document).on('pageinit', "#dlnaProfilePage", function () { $(document).on('pageinit', "#dlnaProfilePage", function() {
var page = this; var page = this;
$('.radioProfileTab', page).on('change', function () { $('.radioProfileTab', page).on('change', function() {
$('.profileTab', page).hide(); $('.profileTab', page).hide();
$('.' + this.value, page).show(); $('.' + this.value, page).show();
}); });
$('#selectDirectPlayProfileType', page).on('change', function () { $('#selectDirectPlayProfileType', page).on('change', function() {
if (this.value == 'Video') { if (this.value == 'Video') {
$('#fldDirectPlayVideoCodec', page).show(); $('#fldDirectPlayVideoCodec', page).show();
@ -504,19 +536,41 @@
}); });
$('.btnAddDirectPlayProfile', page).on('click', function () { $('#selectTranscodingProfileType', page).on('change', function () {
if (this.value == 'Video') {
$('#fldTranscodingVideoCodec', page).show();
} else {
$('#fldTranscodingVideoCodec', page).hide();
}
if (this.value == 'Photo') {
$('#fldTranscodingAudioCodec', page).hide();
} else {
$('#fldTranscodingAudioCodec', page).show();
}
});
$('.btnAddDirectPlayProfile', page).on('click', function() {
editDirectPlayProfile(page); editDirectPlayProfile(page);
}); });
}).on('pageshow', "#dlnaProfilePage", function () { $('.btnAddTranscodingProfile', page).on('click', function () {
editTranscodingProfile(page);
});
}).on('pageshow', "#dlnaProfilePage", function() {
var page = this; var page = this;
loadProfile(page); loadProfile(page);
}).on('pagebeforeshow', "#dlnaProfilePage", function () { }).on('pagebeforeshow', "#dlnaProfilePage", function() {
var page = this; var page = this;
@ -526,8 +580,7 @@
}); });
window.DlnaProfilePage = { window.DlnaProfilePage = {
onSubmit: function() {
onSubmit: function () {
Dashboard.showLoadingMsg(); Dashboard.showLoadingMsg();
@ -539,7 +592,7 @@
return false; return false;
}, },
onDirectPlayFormSubmit: function () { onDirectPlayFormSubmit: function() {
var form = this; var form = this;
var page = $(form).parents('.page'); var page = $(form).parents('.page');
@ -547,6 +600,17 @@
saveDirectPlayProfile(page); saveDirectPlayProfile(page);
return false; return false;
},
onTranscodingProfileFormSubmit: function() {
var form = this;
var page = $(form).parents('.page');
saveTranscodingProfile(page);
return false;
} }
}; };

View file

@ -114,6 +114,7 @@
ApiClient.getLiveTvChannel(currentItem.ChannelId, Dashboard.getCurrentUserId()).done(function (channel) { ApiClient.getLiveTvChannel(currentItem.ChannelId, Dashboard.getCurrentUserId()).done(function (channel) {
var userdata = channel.UserData || {}; var userdata = channel.UserData || {};
LibraryBrowser.showPlayMenu(this, channel.Id, channel.Type, false, channel.MediaType, userdata.PlaybackPositionTicks); LibraryBrowser.showPlayMenu(this, channel.Id, channel.Type, false, channel.MediaType, userdata.PlaybackPositionTicks);
}); });
}); });

View file

@ -200,8 +200,8 @@
var playerInfo = MediaController.getPlayerInfo(); var playerInfo = MediaController.getPlayerInfo();
var html = ''; var html = '';
html += '<h3>Select Player:</h3>';
html += '<fieldset data-role="controlgroup" data-mini="true">'; html += '<fieldset data-role="controlgroup" data-mini="true">';
html += '<legend>Select Player:</legend>';
for (var i = 0, length = targets.length; i < length; i++) { for (var i = 0, length = targets.length; i < length; i++) {
@ -223,14 +223,17 @@
} }
html += '</fieldset>'; html += '</fieldset>';
html += '<p class="fieldDescription">All plays will be sent to the selected player.</p>';
return html; return html;
} }
function showPlayerSelection() { function showPlayerSelection(page) {
var promise = MediaController.getTargets(); var promise = MediaController.getTargets();
var html = '<div data-role="panel" data-position="right" data-display="overlay" id="playerFlyout" data-theme="b">'; var html = '<div data-role="panel" data-position="right" data-display="overlay" data-position-fixed="true" id="playerSelectionPanel" class="playerSelectionPanel" data-theme="b">';
html += '<div class="players"></div>'; html += '<div class="players"></div>';
@ -238,7 +241,7 @@
$(document.body).append(html); $(document.body).append(html);
var elem = $('#playerFlyout').panel({}).trigger('create').panel("open").on("panelafterclose", function () { var elem = $('#playerSelectionPanel').panel({}).trigger('create').panel("open").on("panelafterclose", function () {
$(this).off("panelafterclose").remove(); $(this).off("panelafterclose").remove();
}); });
@ -270,7 +273,7 @@
$('.btnCast', page).on('click', function () { $('.btnCast', page).on('click', function () {
showPlayerSelection(); showPlayerSelection(page);
}); });
}); });

View file

@ -1,11 +1,10 @@
(function ($, document, Dashboard) { (function ($, document, Dashboard) {
var userId;
var getNotificationsSummaryPromise; var getNotificationsSummaryPromise;
function getNotificationsSummary() { function getNotificationsSummary() {
getNotificationsSummaryPromise = getNotificationsSummaryPromise || ApiClient.getNotificationSummary(userId); getNotificationsSummaryPromise = getNotificationsSummaryPromise || ApiClient.getNotificationSummary(Dashboard.getCurrentUserId());
return getNotificationsSummaryPromise; return getNotificationsSummaryPromise;
} }
@ -64,6 +63,9 @@
$('.notificationsFlyout').popup("close"); $('.notificationsFlyout').popup("close");
getNotificationsSummaryPromise = null;
updateNotificationCount();
}); });
}); });
@ -173,8 +175,6 @@
return; return;
} }
userId = user.Id;
$('<a class="imageLink btnNotifications" href="#" title="Notifications">0</a>').insertAfter($('.btnCurrentUser', header)).on('click', showNotificationsFlyout); $('<a class="imageLink btnNotifications" href="#" title="Notifications">0</a>').insertAfter($('.btnCurrentUser', header)).on('click', showNotificationsFlyout);
updateNotificationCount(); updateNotificationCount();

View file

@ -475,7 +475,7 @@ var Dashboard = {
Dashboard.getCurrentUser().done(function (user) { Dashboard.getCurrentUser().done(function (user) {
var html = '<div data-role="panel" data-position="right" data-display="overlay" id="userFlyout" data-theme="a">'; var html = '<div data-role="panel" data-position="right" data-display="overlay" id="userFlyout" data-position-fixed="true" data-theme="a">';
html += '<h3>'; html += '<h3>';

View file

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<packages> <packages>
<package id="MediaBrowser.ApiClient.Javascript" version="3.0.248" targetFramework="net45" /> <package id="MediaBrowser.ApiClient.Javascript" version="3.0.249" targetFramework="net45" />
</packages> </packages>