Added sticky positioning to layout editor toolbox.

This commit is contained in:
Daniel Stolt
2015-06-09 19:38:44 +03:00
parent d86a5ec8c5
commit 452b8cfeac
13 changed files with 171 additions and 426 deletions

View File

@@ -113,7 +113,6 @@
<Content Include="Scripts\LayoutEditor\Services\ScopeConfigurator.js" />
<Content Include="Scripts\Lib.js" />
<Content Include="Scripts\Lib.min.js" />
<Content Include="Scripts\Lib\affix.js" />
<Content Include="Scripts\Lib\angular-resource.js" />
<Content Include="Scripts\Lib\angular-sanitize.js" />
<Content Include="Scripts\Lib\angular.js" />

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -166,7 +166,29 @@
}
],
templateUrl: environment.templateUrl("Toolbox"),
replace: true
replace: true,
link: function (scope, element) {
var toolbox = element.find(".layout-toolbox");
$(window).on("resize scroll", function (e) {
var canvas = element.parent().find(".layout-canvas");
// If the canvas is taller than the toolbox, make the toolbox sticky-positioned within the editor
// to help the user avoid excessive vertical scrolling.
var canvasIsTaller = !!canvas && canvas.height() > toolbox.height();
var windowPos = $(window).scrollTop();
if (canvasIsTaller && windowPos > element.offset().top + element.height() - toolbox.height()) {
toolbox.addClass("sticky-bottom");
toolbox.removeClass("sticky-top");
}
else if (canvasIsTaller && windowPos > element.offset().top) {
toolbox.addClass("sticky-top");
toolbox.removeClass("sticky-bottom");
}
else {
toolbox.removeClass("sticky-top");
toolbox.removeClass("sticky-bottom");
}
});
}
};
}
]);

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,162 +0,0 @@
/* ========================================================================
* Bootstrap: affix.js v3.3.1
* http://getbootstrap.com/javascript/#affix
* ========================================================================
* Copyright 2011-2014 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
* ======================================================================== */
+function ($) {
'use strict';
// AFFIX CLASS DEFINITION
// ======================
var Affix = function (element, options) {
this.options = $.extend({}, Affix.DEFAULTS, options)
this.$target = $(this.options.target)
.on('scroll.bs.affix.data-api', $.proxy(this.checkPosition, this))
.on('click.bs.affix.data-api', $.proxy(this.checkPositionWithEventLoop, this))
this.$element = $(element)
this.affixed =
this.unpin =
this.pinnedOffset = null
this.checkPosition()
}
Affix.VERSION = '3.3.1'
Affix.RESET = 'affix affix-top affix-bottom'
Affix.DEFAULTS = {
offset: 0,
target: window
}
Affix.prototype.getState = function (scrollHeight, height, offsetTop, offsetBottom) {
var scrollTop = this.$target.scrollTop()
var position = this.$element.offset()
var targetHeight = this.$target.height()
if (offsetTop != null && this.affixed == 'top') return scrollTop < offsetTop ? 'top' : false
if (this.affixed == 'bottom') {
if (offsetTop != null) return (scrollTop + this.unpin <= position.top) ? false : 'bottom'
return (scrollTop + targetHeight <= scrollHeight - offsetBottom) ? false : 'bottom'
}
var initializing = this.affixed == null
var colliderTop = initializing ? scrollTop : position.top
var colliderHeight = initializing ? targetHeight : height
if (offsetTop != null && colliderTop <= offsetTop) return 'top'
if (offsetBottom != null && (colliderTop + colliderHeight >= scrollHeight - offsetBottom)) return 'bottom'
return false
}
Affix.prototype.getPinnedOffset = function () {
if (this.pinnedOffset) return this.pinnedOffset
this.$element.removeClass(Affix.RESET).addClass('affix')
var scrollTop = this.$target.scrollTop()
var position = this.$element.offset()
return (this.pinnedOffset = position.top - scrollTop)
}
Affix.prototype.checkPositionWithEventLoop = function () {
setTimeout($.proxy(this.checkPosition, this), 1)
}
Affix.prototype.checkPosition = function () {
if (!this.$element.is(':visible')) return
var height = this.$element.height()
var offset = this.options.offset
var offsetTop = offset.top
var offsetBottom = offset.bottom
var scrollHeight = $('body').height()
if (typeof offset != 'object') offsetBottom = offsetTop = offset
if (typeof offsetTop == 'function') offsetTop = offset.top(this.$element)
if (typeof offsetBottom == 'function') offsetBottom = offset.bottom(this.$element)
var affix = this.getState(scrollHeight, height, offsetTop, offsetBottom)
if (this.affixed != affix) {
if (this.unpin != null) this.$element.css('top', '')
var affixType = 'affix' + (affix ? '-' + affix : '')
var e = $.Event(affixType + '.bs.affix')
this.$element.trigger(e)
if (e.isDefaultPrevented()) return
this.affixed = affix
this.unpin = affix == 'bottom' ? this.getPinnedOffset() : null
this.$element
.removeClass(Affix.RESET)
.addClass(affixType)
.trigger(affixType.replace('affix', 'affixed') + '.bs.affix')
}
if (affix == 'bottom') {
this.$element.offset({
top: scrollHeight - height - offsetBottom
})
}
}
// AFFIX PLUGIN DEFINITION
// =======================
function Plugin(option) {
return this.each(function () {
var $this = $(this)
var data = $this.data('bs.affix')
var options = typeof option == 'object' && option
if (!data) $this.data('bs.affix', (data = new Affix(this, options)))
if (typeof option == 'string') data[option]()
})
}
var old = $.fn.affix
$.fn.affix = Plugin
$.fn.affix.Constructor = Affix
// AFFIX NO CONFLICT
// =================
$.fn.affix.noConflict = function () {
$.fn.affix = old
return this
}
// AFFIX DATA-API
// ==============
$(window).on('load', function () {
$('[data-spy="affix"]').each(function () {
var $spy = $(this)
var data = $spy.data()
data.offset = data.offset || {}
if (data.offsetBottom != null) data.offset.bottom = data.offsetBottom
if (data.offsetTop != null) data.offset.top = data.offsetTop
Plugin.call($spy, data)
})
})
}(jQuery);

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -4,7 +4,7 @@
display: flex;
margin-top: 1em;
font-size: @font-size;
align-items: flex-start;
align-items: stretch;
> .layout-canvas-wrapper {
flex-grow: 1;

View File

@@ -1,66 +1,81 @@
@import "Variables.less";
.layout-editor {
> .layout-toolbox {
> .layout-toolbox-wrapper {
position: relative;
margin-left: @container-padding;
border: 1px solid @gray-border;
width: 220px;
flex-shrink: 0;
min-height: 400px;
padding: @container-padding / 2;
background-color: @gray-bg;
.layout-toolbox-group {
margin-top: @container-padding;
> .layout-toolbox {
border: 1px solid @gray-border;
width: 220px;
min-height: 400px;
padding: @container-padding / 2;
background-color: @gray-bg;
.layout-toolbox-group-heading {
display: block;
margin-bottom: @container-padding / 3;
text-decoration: none;
&.sticky-top {
position: fixed;
top: 0;
}
&.sticky-bottom {
position: absolute;
bottom: 0;
}
.layout-toolbox-group {
margin-top: @container-padding;
.layout-toolbox-group-heading {
display: block;
margin-bottom: @container-padding / 3;
text-decoration: none;
&:before {
display: inline-block;
width: 10px;
margin-right: @container-padding / 3;
font: normal normal normal 14px/1 FontAwesome;
text-align: center;
content: "\f0d7";
&:before {
display: inline-block;
width: 10px;
margin-right: @container-padding / 3;
font: normal normal normal 14px/1 FontAwesome;
text-align: center;
content: "\f0d7";
}
}
}
&.collapsed {
.layout-toolbox-group-heading:before {
content: "\f0da";
&.collapsed {
.layout-toolbox-group-heading:before {
content: "\f0da";
}
.layout-toolbox-items {
display: none;
}
}
.layout-toolbox-items {
display: none;
}
}
.layout-toolbox-section + .layout-toolbox-section {
margin-top: @container-padding / 3;
}
.layout-toolbox-item {
border: 1px solid @gray-border;
background-color: #fff;
padding: (@container-padding - 3) @container-padding;
cursor: default;
i {
display: inline-block;
width: 16px;
font: normal normal normal 14px/1 FontAwesome;
}
+ .layout-toolbox-item {
.layout-toolbox-section + .layout-toolbox-section {
margin-top: @container-padding / 3;
}
}
+ .layout-toolbox-group {
margin-top: @container-padding / 2;
.layout-toolbox-item {
border: 1px solid @gray-border;
background-color: #fff;
padding: (@container-padding - 3) @container-padding;
cursor: default;
i {
display: inline-block;
width: 16px;
font: normal normal normal 14px/1 FontAwesome;
}
+ .layout-toolbox-item {
margin-top: @container-padding / 3;
}
}
+ .layout-toolbox-group {
margin-top: @container-padding / 2;
}
}
}
}

View File

@@ -1,23 +1,25 @@
<div class="layout-toolbox">
<label>
<input type="checkbox" ng-checked="element.inlineEditingIsActive" ng-click="toggleInlineEditing()" />
@T("Edit HTML content inline")
</label>
<ul class="layout-toolbox-groups" ng-hide="element.inlineEditingIsActive">
<li class="layout-toolbox-group" ng-class="{collapsed: layoutIsCollapsed}">
<a href="#" class="layout-toolbox-group-heading" ng-click="toggleLayoutIsCollapsed($event)">@T("Layout")</a>
<ul class="layout-toolbox-items">
<section class="layout-toolbox-section layout-toolbox-section-grid" ng-model="gridElements" ui-sortable="getSortableOptions('Grid')">
<li class="layout-toolbox-item layout-toolbox-item-grid" title="{{item.toolboxDescription}}" ng-repeat="item in gridElements"><i>{{item.toolboxIcon}}</i> {{item.toolboxLabel}}</li>
</section>
<section class="layout-toolbox-section layout-toolbox-section-row" ng-model="rowElements" ui-sortable="getSortableOptions('Row')">
<li class="layout-toolbox-item layout-toolbox-item-row" title="{{item.toolboxDescription}}" ng-repeat="item in rowElements"><i>{{item.toolboxIcon}}</i> {{item.toolboxLabel}}</li>
</section>
<section class="layout-toolbox-section layout-toolbox-section-column" ng-model="columnElements" ui-sortable="getSortableOptions('Column')">
<li class="layout-toolbox-item layout-toolbox-item-column" title="{{item.toolboxDescription}}" ng-repeat="item in columnElements"><i>{{item.toolboxIcon}}</i> {{item.toolboxLabel}}</li>
</section>
</ul>
</li>
<orc-layout-toolbox-group category="category" ng-repeat="category in contentElementCategories" />
</ul>
<div class="layout-toolbox-wrapper">
<div class="layout-toolbox">
<label>
<input type="checkbox" ng-checked="element.inlineEditingIsActive" ng-click="toggleInlineEditing()" />
@T("Edit HTML content inline")
</label>
<ul class="layout-toolbox-groups" ng-hide="element.inlineEditingIsActive">
<li class="layout-toolbox-group" ng-class="{collapsed: layoutIsCollapsed}">
<a href="#" class="layout-toolbox-group-heading" ng-click="toggleLayoutIsCollapsed($event)">@T("Layout")</a>
<ul class="layout-toolbox-items">
<section class="layout-toolbox-section layout-toolbox-section-grid" ng-model="gridElements" ui-sortable="getSortableOptions('Grid')">
<li class="layout-toolbox-item layout-toolbox-item-grid" title="{{item.toolboxDescription}}" ng-repeat="item in gridElements"><i>{{item.toolboxIcon}}</i> {{item.toolboxLabel}}</li>
</section>
<section class="layout-toolbox-section layout-toolbox-section-row" ng-model="rowElements" ui-sortable="getSortableOptions('Row')">
<li class="layout-toolbox-item layout-toolbox-item-row" title="{{item.toolboxDescription}}" ng-repeat="item in rowElements"><i>{{item.toolboxIcon}}</i> {{item.toolboxLabel}}</li>
</section>
<section class="layout-toolbox-section layout-toolbox-section-column" ng-model="columnElements" ui-sortable="getSortableOptions('Column')">
<li class="layout-toolbox-item layout-toolbox-item-column" title="{{item.toolboxDescription}}" ng-repeat="item in columnElements"><i>{{item.toolboxIcon}}</i> {{item.toolboxLabel}}</li>
</section>
</ul>
</li>
<orc-layout-toolbox-group category="category" ng-repeat="category in contentElementCategories" />
</ul>
</div>
</div>

View File

@@ -96,8 +96,7 @@ var srcJsLib = [
"Scripts/Lib/angular.js",
"Scripts/Lib/angular-sanitize.js",
"Scripts/Lib/angular-resource.js",
"Scripts/Lib/sortable.js",
"Scripts/Lib/affix.js"
"Scripts/Lib/sortable.js"
];
var srcJsLayoutEditor = [