diff --git a/src/Umbraco.Core/Constants-Applications.cs b/src/Umbraco.Core/Constants-Applications.cs
index 12f7076fc4..fb1fb42044 100644
--- a/src/Umbraco.Core/Constants-Applications.cs
+++ b/src/Umbraco.Core/Constants-Applications.cs
@@ -73,6 +73,11 @@
///
public const string DataTypes = "dataTypes";
+ ///
+ /// alias for the packages tree
+ ///
+ public const string Packages = "packager";
+
///
/// alias for the dictionary tree.
///
diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/ourpackagerrepository.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/ourpackagerrepository.resource.js
new file mode 100644
index 0000000000..d9307c7c28
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/common/resources/ourpackagerrepository.resource.js
@@ -0,0 +1,64 @@
+/**
+ * @ngdoc service
+ * @name umbraco.resources.ourPackageRepositoryResource
+ * @description handles data for package installations
+ **/
+function ourPackageRepositoryResource($q, $http, umbDataFormatter, umbRequestHelper) {
+
+ var baseurl = "https://our.umbraco.org/webapi/packages/v1";
+
+ return {
+
+ getDetails: function (packageId) {
+
+ return umbRequestHelper.resourcePromise(
+ $http.get(baseurl + "/" + packageId),
+ 'Failed to get package details');
+ },
+
+ getCategories: function () {
+
+ return umbRequestHelper.resourcePromise(
+ $http.get(baseurl),
+ 'Failed to query packages');
+ },
+
+ getPopular: function (maxResults, category) {
+
+ if (maxResults === undefined) {
+ maxResults = 10;
+ }
+ if (category === undefined) {
+ category = "";
+ }
+
+ return umbRequestHelper.resourcePromise(
+ $http.get(baseurl + "?pageIndex=0&pageSize=" + maxResults + "&category=" + category + "&order=Popular"),
+ 'Failed to query packages');
+ },
+
+ search: function (pageIndex, pageSize, category, query, canceler) {
+
+ var httpConfig = {};
+ if (canceler) {
+ httpConfig["timeout"] = canceler;
+ }
+
+ if (category === undefined) {
+ category = "";
+ }
+ if (query === undefined) {
+ query = "";
+ }
+
+ return umbRequestHelper.resourcePromise(
+ $http.get(baseurl + "?pageIndex=" + pageIndex + "&pageSize=" + pageSize + "&category=" + category + "&query=" + query),
+ httpConfig,
+ 'Failed to query packages');
+ }
+
+
+ };
+}
+
+angular.module('umbraco.resources').factory('ourPackageRepositoryResource', ourPackageRepositoryResource);
diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/package.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/package.resource.js
index c1d95861eb..33889e35e4 100644
--- a/src/Umbraco.Web.UI.Client/src/common/resources/package.resource.js
+++ b/src/Umbraco.Web.UI.Client/src/common/resources/package.resource.js
@@ -7,6 +7,31 @@ function packageResource($q, $http, umbDataFormatter, umbRequestHelper) {
return {
+ /**
+ * @ngdoc method
+ * @name umbraco.resources.packageInstallResource#getInstalled
+ * @methodOf umbraco.resources.packageInstallResource
+ *
+ * @description
+ * Gets a list of installed packages
+ */
+ getInstalled: function() {
+ return umbRequestHelper.resourcePromise(
+ $http.get(
+ umbRequestHelper.getApiUrl(
+ "packageInstallApiBaseUrl",
+ "GetInstalled")),
+ 'Failed to get installed packages');
+ },
+
+ uninstall: function(packageId) {
+ return umbRequestHelper.resourcePromise(
+ $http.post(
+ umbRequestHelper.getApiUrl(
+ "packageInstallApiBaseUrl",
+ "Uninstall", { packageId: packageId })),
+ 'Failed to uninstall package');
+ },
/**
* @ngdoc method
diff --git a/src/Umbraco.Web.UI.Client/src/common/services/util.service.js b/src/Umbraco.Web.UI.Client/src/common/services/util.service.js
index 9fbf2947af..30815027b0 100644
--- a/src/Umbraco.Web.UI.Client/src/common/services/util.service.js
+++ b/src/Umbraco.Web.UI.Client/src/common/services/util.service.js
@@ -1,4 +1,62 @@
/*Contains multiple services for various helper tasks */
+function versionHelper() {
+
+ return {
+
+ //see: https://gist.github.com/TheDistantSea/8021359
+ versionCompare: function(v1, v2, options) {
+ var lexicographical = options && options.lexicographical,
+ zeroExtend = options && options.zeroExtend,
+ v1parts = v1.split('.'),
+ v2parts = v2.split('.');
+
+ function isValidPart(x) {
+ return (lexicographical ? /^\d+[A-Za-z]*$/ : /^\d+$/).test(x);
+ }
+
+ if (!v1parts.every(isValidPart) || !v2parts.every(isValidPart)) {
+ return NaN;
+ }
+
+ if (zeroExtend) {
+ while (v1parts.length < v2parts.length) {
+ v1parts.push("0");
+ }
+ while (v2parts.length < v1parts.length) {
+ v2parts.push("0");
+ }
+ }
+
+ if (!lexicographical) {
+ v1parts = v1parts.map(Number);
+ v2parts = v2parts.map(Number);
+ }
+
+ for (var i = 0; i < v1parts.length; ++i) {
+ if (v2parts.length === i) {
+ return 1;
+ }
+
+ if (v1parts[i] === v2parts[i]) {
+ continue;
+ }
+ else if (v1parts[i] > v2parts[i]) {
+ return 1;
+ }
+ else {
+ return -1;
+ }
+ }
+
+ if (v1parts.length !== v2parts.length) {
+ return -1;
+ }
+
+ return 0;
+ }
+ };
+}
+angular.module('umbraco.services').factory('versionHelper', versionHelper);
function packageHelper(assetsService, treeService, eventsService, $templateCache) {
diff --git a/src/Umbraco.Web.UI.Client/src/less/belle.less b/src/Umbraco.Web.UI.Client/src/less/belle.less
index 27bceeeb58..0399fe8f8f 100644
--- a/src/Umbraco.Web.UI.Client/src/less/belle.less
+++ b/src/Umbraco.Web.UI.Client/src/less/belle.less
@@ -108,6 +108,8 @@
@import "components/umb-empty-state.less";
@import "components/umb-property-editor.less";
@import "components/umb-iconpicker.less";
+@import "components/umb-packages.less";
+@import "components/umb-package-local-install.less";
@import "components/buttons/umb-button.less";
@import "components/buttons/umb-button-group.less";
diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-package-local-install.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-package-local-install.less
new file mode 100644
index 0000000000..a272a40c90
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-package-local-install.less
@@ -0,0 +1,153 @@
+//
+// Install local package
+//
+
+// Helpers
+.faded {
+ color: @grayMed;
+}
+
+.inline-flex {
+ display: inline-flex;
+}
+
+.mt-3 {
+ margin-top: 30px;
+}
+
+// Upload state
+
+.umb-upload-local,
+.umb-info-local {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ text-align: center;
+}
+
+.umb-upload-local form,
+.umb-info-local-items {
+ display: flex;
+ flex-direction: column;
+
+ justify-content: center;
+ align-items: center;
+
+ margin: 0;
+
+ max-width: 640px;
+}
+
+.umb-upload-local__dropzone {
+ position: relative;
+ width: 500px;
+ height: 300px;
+ border: 2px dashed @grayLight;
+ border-radius: 3px;
+ background: @grayLighter;
+
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+
+ margin-bottom: 30px;
+
+ transition: 100ms box-shadow ease, 100ms border ease;
+
+ &:hover,
+ &.drag-over {
+ border-color: @blue;
+ border-style: solid;
+ box-shadow: 0 3px 8px rgba(0,0,0, .1);
+ transition: 100ms box-shadow ease, 100ms border ease;
+ }
+}
+
+.umb-upload-local__dropzone i {
+ display: block;
+ color: @grayLight;
+ font-size: 110px;
+ line-height: 1;
+}
+
+.umb-upload-local__select-file {
+ font-weight: bold;
+ color: @blue;
+ cursor: pointer;
+}
+
+
+// Accept terms
+.umb-accept-terms {
+ display: flex;
+ align-items: center;
+
+ font-size: 13px;
+}
+
+.umb-package-installer-label .label-text {
+ margin-left: 5px;
+}
+
+.umb-package-installer-label input[type="radio"],
+.umb-package-installer-label input[type="checkbox"] {
+ margin-top: 0px;
+}
+
+.umb-package-installer-label {
+ display: inline-flex;
+ align-items: center;
+
+ font-size: 13px;
+ user-select: none;
+}
+
+
+// Info state
+.umb-info-local-items {
+ border: 2px solid @grayLight;
+ border-radius: 3px;
+ background: @grayLighter;
+
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+
+ margin: 0 20px;
+ max-width: 540px;
+}
+
+.umb-info-local-items a {
+ text-decoration: underline;
+
+ &:hover {
+ text-decoration: none;
+ }
+}
+
+.umb-info-local-item {
+ margin-bottom: 20px;
+}
+
+.umb-info-local-items .umb-package-icon {
+ width: 100%;
+ box-sizing: border-box;
+ min-height: 150px;
+ font-size: 60px;
+}
+
+.umb-info-local-items .umb-package-icon img {
+ max-width: 100px;
+}
+
+.umb-info-local-items .umb-package-info {
+ width: 100%;
+ box-sizing: border-box;
+ padding: 20px 40px;
+}
+
+.umb-info-local-items .umb-package-installer-label {
+ margin-left: 10px;
+}
diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-packages.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-packages.less
new file mode 100644
index 0000000000..41ea5e5fd1
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-packages.less
@@ -0,0 +1,520 @@
+.umb-packages-view-title {
+ font-size: 20px;
+ font-weight: bold;
+ color: @black;
+ margin-bottom: 30px;
+}
+
+.umb-packages-view-wrapper {
+ padding: 20px 60px;
+}
+
+.umb-packages-section {
+ margin-bottom: 40px;
+}
+
+.umb-packages-search {
+ width: 100%;
+ background: @grayLighter;
+ border-radius: 3px;
+ padding: 30px;
+ box-sizing: border-box;
+}
+
+.umb-packages-search input {
+ border-width: 2px;
+ border-radius: 3px;
+ min-height: 44px;
+
+ padding: 4px 10px;
+ font-size: 16px;
+ border-color: #ececec;
+ margin-bottom: 0;
+ border-color: @grayLight;
+
+ &:hover, &:focus {
+ border-color: @grayLight;
+ }
+}
+
+.umb-packages__pagination {
+ display: flex;
+ justify-content: center;
+}
+
+.umb-packages {
+ margin: 0 -10px;
+ display: flex;
+ flex-wrap: wrap;
+}
+
+// Cards
+.umb-package {
+ padding: 10px;
+ box-sizing: border-box;
+ width: 25%;
+}
+
+.umb-package-link {
+ display: flex;
+ flex-wrap: wrap;
+ flex-direction: column;
+ justify-content: center;
+
+ position: relative;
+ box-sizing: border-box;
+
+ height: 100%;
+ width: 100%;
+
+ border: 1px solid #ececec;
+ border-radius: 3px;
+
+ text-decoration: none !important;
+
+ transition: border-color 100ms ease;
+
+ &:hover {
+ border-color: @blue;
+ }
+}
+
+
+
+// Icon
+.umb-package-icon {
+ display: flex;
+
+ justify-content: center;
+ align-items: center;
+
+ padding-top: 10px;
+ padding-right: 10px;
+ padding-left: 10px;
+ padding-bottom: 10px;
+
+ text-align: center;
+ background-color: white;
+
+ min-height: 60px;
+}
+
+.umb-package-icon img {
+ max-width: 70px;
+ height: auto;
+}
+
+
+// Info
+.umb-package-info {
+ padding-right: 15px;
+ padding-bottom: 15px;
+ padding-left: 15px;
+ padding-top: 15px;
+ text-align: center;
+ background: @grayLighter;
+ border-top: 1px solid #ececec;
+}
+
+
+// Name
+.umb-package-name {
+ font-size: 14px;
+ max-width: 250px;
+ margin-bottom: 5px;
+
+ font-weight: bold;
+
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+
+ line-height: normal;
+ margin-left: auto;
+ margin-right: auto;
+}
+
+.umb-package-description {
+ font-size: 11px;
+ color: @grayMed;
+ word-wrap: break-word;
+ line-height: 1.1rem;
+}
+
+// Numbers
+.umb-package-numbers {
+ display: flex;
+ flex-wrap: wrap;
+ flex-direction: row;
+ justify-content: center;
+
+ opacity: .6;
+
+ margin-top: 10px;
+}
+
+.umb-package-numbers small {
+ padding: 0 5px;
+
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.umb-package-numbers i {
+ font-size: 14px;
+}
+
+.umb-package-link:hover .umb-package-numbers {
+ opacity: 1;
+}
+
+.umb-package-link:hover .umb-package-numbers .icon-hearts {
+ color: red !important;
+}
+
+// Version
+.umb-package-version {
+ display: inline-flex;
+ font-size: 11px;
+ font-weight: bold;
+ padding: 1px 5px;
+ background: #ececec;
+ border-radius: 3px;
+ color: black;
+}
+
+/* umb-buttons-era */
+.umb-era-button {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+
+ font-size: 14px;
+ font-weight: bold;
+
+ height: 38px;
+ line-height: 1;
+
+ max-width: 100%;
+ padding: 0 18px;
+
+ color: #484848;
+ background-color: #e0e0e0;
+
+ text-decoration: none !important;
+ user-select: none;
+
+ white-space: nowrap;
+ overflow: hidden;
+
+ border-radius: 3px;
+ border: 0 none;
+ box-sizing: border-box;
+
+ cursor: pointer;
+
+ transition: background-color 80ms ease, color 80ms ease;
+}
+
+
+.umb-era-button:hover,
+.umb-era-button:active {
+ color: #484848;
+ background-color: #d3d3d3;
+ outline: none;
+ text-decoration: none;
+}
+
+
+.umb-era-button:focus {
+ outline: none;
+}
+
+.umb-era-button.-blue {
+ background: @blue;
+ color: white;
+}
+
+.umb-era-button.-blue:hover {
+ background-color: @blueDark;
+}
+
+.umb-era-button.-link {
+ padding: 0;
+ background: transparent;
+}
+
+.umb-era-button.-link:hover {
+ background-color: transparent;
+ opacity: .6;
+}
+
+.umb-era-button.-inactive {
+ cursor: not-allowed;
+ color: #BBB;
+ background: #EAE7E7;
+}
+
+.umb-era-button.-inactive:hover {
+ color: #BBB;
+ background: #EAE7E7;
+}
+
+
+.umb-era-button.-full-width {
+ display: block;
+ width: 100%;
+}
+
+
+/* CATEGORIES */
+
+.umb-packages-categories {
+ display: flex;
+ user-select: center;
+ flex-wrap: wrap;
+}
+
+.umb-packages-category {
+ display: flex;
+ align-items: center;
+ flex: 1 0 auto;
+ justify-content: center;
+ max-width: 25%;
+ font-size: 14px;
+ font-weight: bold;
+ color: @black;
+ box-sizing: border-box;
+ justify-content: center;
+ border-top: 1px solid @grayLight;
+ border-bottom: 1px solid @grayLight;
+ border-right: 1px solid @grayLight;
+ padding: 10px 0;
+}
+
+.umb-packages-category:hover,
+.umb-packages-category.-active {
+ text-decoration: none;
+ color: @blue;
+}
+
+.umb-packages-category.-first {
+ border-left: 1px solid @grayLight;
+ border-radius: 3px 0 0 3px;
+}
+
+.umb-packages-category.-last {
+ border-right: 1px solid @grayLight;
+ border-radius: 0 3px 3px 0;
+}
+
+/* PACKAGE DETAILS */
+
+.umb-package-details {
+ display: flex;
+}
+
+.umb-package-details__back-link {
+ font-weight: bold;
+ color: @grayMed;
+}
+
+.umb-package-details__back-link:hover {
+ color: @black;
+ text-decoration: none;
+}
+
+.umb-package-details__main-content {
+ flex: 1 1 auto;
+ margin-right: 40px;
+}
+
+.umb-package-details__sidebar {
+ flex: 0 0 350px;
+}
+
+.umb-package-details__section {
+ background: @grayLighter;
+ padding: 20px;
+ margin-bottom: 20px;
+ border-radius: 3px;
+}
+
+.umb-package-details__section-title {
+ font-size: 17px;
+ font-weight: bold;
+ color: black;
+ margin-top: 0;
+ margin-bottom: 15px;
+}
+
+.umb-package-details__section-description {
+ font-size: 12px;
+ line-height: 1.6em;
+ margin-bottom: 15px;
+}
+
+.umb-package-details__information-item {
+ display: flex;
+ margin-bottom: 5px;
+ font-size: 13px;
+}
+
+.umb-package-details__information-item-label {
+ color: black;
+ font-weight: bold;
+ margin-right: 3px;
+}
+
+.umb-package-details__information-item-label-2 {
+ font-size: 12px;
+ color: @grayMed;
+}
+
+.umb-package-details__compatability {
+ margin-bottom: 15px;
+}
+
+.umb-package-details__compatability-label {
+ margin-bottom: 3px;
+}
+
+.umb-package-details__description {
+ margin-bottom: 20px;
+ line-height: 1.6em;
+}
+
+.umb-package-details__description p {
+ margin-bottom: 20px;
+}
+
+/* Links */
+
+.umb-package-details__link {
+ color: #DA3287;
+}
+
+.umb-package-details__link:hover {
+ color: #B32D71;
+ text-decoration: none;
+}
+
+/* Owner profile */
+
+.umb-package-details__owner-profile {
+ display: flex;
+ align-items: center;
+}
+.umb-package-details__owner-profile-avatar {
+ margin-right: 15px;
+}
+
+.umb-package-details__owner-profile-name {
+ font-size: 15px;
+ color: #000000;
+ font-weight: bold;
+}
+
+.umb-package-details__owner-profile-karma {
+ font-size: 12px;
+ color: @grayMed;
+}
+
+/* gallery */
+
+.umb-gallery__thumbnails {
+ display: flex;
+ flex-wrap: wrap;
+}
+
+.umb-gallery__thumbnail {
+ flex: 1 1 100px;
+ border: 1px solid @grayLight;
+ border-radius: 3px;
+ margin: 5px;
+ padding: 10px;
+ box-sizing: border-box;
+}
+
+.umb-gallery__thumbnail:hover {
+ cursor: pointer;
+ border-color: @blue;
+}
+
+/* Avatar */
+
+.umb-avatar {
+ border-radius: 50%;
+ width: 50px;
+}
+
+/* Progress bar */
+
+.umb-progress-bar {
+ background: @grayLight;
+ width: 100%;
+ display: block;
+ height: 10px;
+ border-radius: 10px;
+ box-sizing: border-box;
+ position: relative;
+ overflow: hidden;
+}
+
+.umb-progress-bar__progress {
+ background: #50C878;
+ position: absolute;
+ left: 0;
+ top: 0;
+ bottom: 0;
+ width: 100%;
+ border-radius: 10px;
+}
+
+/* PACKAGE LIST */
+
+.umb-package-list {
+ display: flex;
+ flex-direction: column;
+}
+
+.umb-package-list__item {
+ display: flex;
+ flex-direction: row;
+ background: @grayLighter;
+ margin-bottom: 10px;
+ border-radius: 3px;
+ padding: 20px;
+ align-items: center;
+}
+
+.umb-package-list__item-icon {
+ flex: 0 0 50px;
+ margin-right: 20px;
+ font-size: 30px;
+ text-align: center;
+}
+
+.umb-package-list__item-content {
+ flex: 1 1 auto;
+ margin-right: 20px;
+}
+
+.umb-package-list__item-name {
+ font-size: 16px;
+ margin-bottom: 5px;
+ color: @black;
+ font-weight: bold;
+}
+
+.umb-package-list__item-description {
+ font-size: 14px;
+ color: @grayMed;
+}
+
+.umb-package-list__item-actions {
+ flex: 1 1 auto;
+ display: flex;
+ justify-content: flex-end;
+}
diff --git a/src/Umbraco.Web.UI.Client/src/less/panel.less b/src/Umbraco.Web.UI.Client/src/less/panel.less
index a1776bf12d..83b61e48a5 100644
--- a/src/Umbraco.Web.UI.Client/src/less/panel.less
+++ b/src/Umbraco.Web.UI.Client/src/less/panel.less
@@ -366,17 +366,17 @@
flex: 1;
}
-.umb-panel-header-content.-top-position {
- position: relative;
- top: -12px;
-}
-
.umb-panel-header-left-side {
display: flex;
flex: 1;
flex-direction: row;
}
+.umb-panel-header-left-side.-top-position {
+ position: relative;
+ top: -12px;
+}
+
.umb-panel-header-icon {
cursor: pointer;
margin-right: 5px;
diff --git a/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-header.html b/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-header.html
index fd112a018a..46c666b8cc 100644
--- a/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-header.html
+++ b/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-header.html
@@ -2,9 +2,9 @@