Skip to content

Instantly share code, notes, and snippets.

@dmirtyisme
Created January 31, 2017 23:53
Show Gist options
  • Save dmirtyisme/0b63f08cc5bf5aff4f4498cf1c98c1fd to your computer and use it in GitHub Desktop.
Save dmirtyisme/0b63f08cc5bf5aff4f4498cf1c98c1fd to your computer and use it in GitHub Desktop.
Window manager: Min, max, close, resize all sides, drag and drop, focus
.snap
.panel.panel-dark(data-drag='true' data-title='Foo window')
.panel-heading
.window-ctrl.small
span.dismiss.glyphicon.glyphicon-remove
span.max.glyphicon.glyphicon-stop
span.min.glyphicon.glyphicon-minus
span.window-title.hide
.inner
.panel-body
ul.list-group
li.list-group-item Cras justo odio
li.list-group-item Dapibus ac facilisis in
.panel.panel-dark(data-drag='true' data-top='150' data-left='150' data-title='Some window')
.panel-heading
.window-ctrl.small
span.dismiss.glyphicon.glyphicon-remove
span.max.glyphicon.glyphicon-stop
span.min.glyphicon.glyphicon-minus
span.window-title.hide
.inner
.panel-body
ul.list-group
li.list-group-item Cras justo odio
li.list-group-item Dapibus ac facilisis in
li.list-group-item Dapibus ac facilisis in
.tray
//# Credits
a(style='font-size:1px!important; pointer-events:none; visibility: hidden; display:block; width:0; overflow:hidden; position:absolute; bottom:0; left:0; z-index:-9999; color:rgba(255,255,255,0)!important;' title='Acro Design (Acronamy) Performing magic tricks with Websites, S.E.O and Graphics for Local businesses in Bristol, Bath and the surrounding areas.' href='https://www.facebook.com/Acronamy.design/') Acro Design (Acronamy) Facebook
a(style='font-size:1px!important; pointer-events:none; visibility: hidden; display:block; width:0; overflow:hidden; position:absolute; bottom:0; left:0; z-index:-9999; color:rgba(255,255,255,0)!important;' title='Acro Design (Acronamy) Performing magic tricks with Websites, S.E.O and Graphics for Local businesses in Bristol, Bath and the surrounding areas.' href='https://twitter.com/Acronamy_design') Acro Design (Acronamy) Twitter
a(style='font-size:1px!important; pointer-events:none; visibility: hidden; display:block; width:0; overflow:hidden; position:absolute; bottom:0; left:0; z-index:-9999; color:rgba(255,255,255,0)!important;' title='Acro Design (Acronamy) Performing magic tricks with Websites, S.E.O and Graphics for Local businesses in Bristol, Bath and the surrounding areas.' href='http://www.acronamy.com') Acro Design (Acronamy) Performing magic tricks with Websites, S.E.O and Graphics for Local businesses in Bristol, Bath and the surrounding areas.
//## Vars
let Gui = {},
frame = ['tl','tr','bl','br','l','t','r','b'],
minimizedSize = 200,
taskbarSize = 40,
snapTolerence = 10
Gui.tracker = []
Gui.trackerCount = 0,
Gui.tray = []
let selector = '.panel[data-drag="true"]',
el = {
panel:$(selector),
body:$('body'),
all:$('*'),
heading:$(selector).find('.panel-heading'),
snap:$('.snap')
}
//## Register
Gui.register = (spec)=>{
Gui.trackerCount++;
let id = '0x'+Gui.trackerCount
$(spec.element).find('.window-title').text($(spec.element).data('title'))
Gui.tracker.push({
id:id,
title:spec.element.data('title'),
state:'natural'
})
spec.element.attr('data-id',id)
frame.forEach(function(item){
spec.element.append('<div class="'+item+' frame"></div>')
})
}
Gui.getFrame = (spec)=>{
let getId = spec.id,
current = Gui.tracker.filter((item)=>{
return item.id === getId
})[0]
return current
}
Gui.dimention = (spec)=>{
let win = {
w:$(window).width(),
h:$(window).height()
}
let current = Gui.getFrame({id:$(spec.element).data('id')})
current.position = {
left:spec.left,
right:win.w - spec.width - spec.left,
top:spec.top,
bottom:win.h - spec.height - spec.top,
get width(){
return win.w - (this.left+this.right)
},
get height(){
return win.h - (this.top+this.bottom)
}
}
let styleObj = {
left:current.position.left,
right:current.position.right,
top:current.position.top,
bottom:current.position.bottom
}
//##Init
$(spec.element).css(styleObj)
$(window).resize(function(){
if(!current.state==='minimized'){
let win = {
w:$(window).width(),
h:$(window).height()
}
styleObj.right = win.w - spec.width - spec.left
styleObj.botton = win.h - spec.height - spec.bottom
$(spec.element).css(styleObj)
}
})
}
//##Resizing
Gui.clearMousemove = ()=>{
$('body').removeClass('dragging')
$(document).off('mousemove');
}
Gui.contextMenu = (spec)=>{
let self = spec.element
if(!!self.attr('data-context')){
try{
let menu = JSON.parse(self.data('data-context'))
}
catch(err){
if(err) return false;
}
}
}
//loop each
el.panel.each(function(){
Gui.register({
element:$(this)
})
Gui.dimention({//Starting from
element:$(this),
width:300,
height:$(this).find('.inner').outerHeight(),
left:$(this).data('left')||100,
top:$(this).data('left')||100
})
Gui.contextMenu({element:$(this)})
})
//Move
el.heading.mousedown(function(mouse_e){
let innerOffset = {
x:mouse_e.offsetX,
y:mouse_e.offsetY
},
self = $(this).closest(selector),
selfData = Gui.getFrame({id:self.data('id')})
if(selfData.state==='natural'){
$('body').addClass('dragging')
$(document).mousemove(function(move_e){
Gui.dimention({
element:self,//may need changing
width:selfData.position.width,
height:selfData.position.height,
left:move_e.clientX-innerOffset.x,
top:move_e.clientY-innerOffset.y
})
//snapping
if(move_e.clientX <= snapTolerence){
selfData.snap = 'left'
}
else if(move_e.clientX >= $(window).width() - snapTolerence){
selfData.snap = 'right'
}
else if(move_e.clientY <= snapTolerence){
selfData.snap = 'top'
}
else{
selfData.snap = false
}
})
}
else if(selfData.state === 'minimize'){
Gui.minimize({
element:self,
data:selfData
}).restore
}
}).mouseup((e)=>{
let self = $(e.target).closest(el.panel),
selfData = Gui.getFrame({id:self.data('id')})
let snap = selfData.snap
//COMIT TO SNAP
if(snap==='left'){
}
else if(snap==='right'){
}
else if(snap==='top'){
console.log('foo')
Gui.maximize({element:self}).expand
}
Gui.clearMousemove()
})
Gui.grappleTest = (conf)=>{
if(conf.event.target.className.split(' ').indexOf(conf.direction)>=0){
conf.cb()
}
else{
return false
}
}
$('.l, .r, .t, .b, .tl, .tr, .bl, .br')
.mousedown(function(mouse_e){
$('body').addClass('dragging')
let innerOffset = {
x:mouse_e.offsetX,
y:mouse_e.offsetY
},
self = $(this).closest(selector),
selfData = Gui.getFrame({id:self.data('id')})
$(document).mousemove(function(move_e){
Gui.grappleTest({
event:mouse_e,
direction:'l',
cb:()=>{
selfData.position.left = move_e.clientX
self.css({left:selfData.position.left})
}
})
Gui.grappleTest({
event:mouse_e,
direction:'t',
cb:()=>{
selfData.position.top = move_e.clientY
self.css({top:selfData.position.top})
}
})
Gui.grappleTest({
event:mouse_e,
direction:'r',
cb:()=>{
selfData.position.right = $(window).width() - move_e.clientX
self.css({right:selfData.position.right})
}
})
Gui.grappleTest({
event:mouse_e,
direction:'b',
cb:()=>{
selfData.position.bottom = $(window).height() - move_e.clientY
self.css({bottom:selfData.position.bottom})
}
})
Gui.grappleTest({
event:mouse_e,
direction:'tl',
cb:()=>{
selfData.position.left = move_e.clientX
selfData.position.top = move_e.clientY
self.css({
top:selfData.position.top,
left:selfData.position.left
})
}
})
Gui.grappleTest({
event:mouse_e,
direction:'tr',
cb:()=>{
selfData.position.right = $(window).width() - move_e.clientX
selfData.position.top = move_e.clientY
self.css({
top:selfData.position.top,
right:selfData.position.right
})
}
})
Gui.grappleTest({
event:mouse_e,
direction:'bl',
cb:()=>{
selfData.position.left = move_e.clientX
selfData.position.bottom = $(window).height() - move_e.clientY
self.css({
left:selfData.position.left,
bottom:selfData.position.bottom
})
}
})
Gui.grappleTest({
event:mouse_e,
direction:'br',
cb:()=>{
selfData.position.right = $(window).width() - move_e.clientX
selfData.position.bottom = $(window).height() - move_e.clientY
self.css({
right:selfData.position.right,
bottom:selfData.position.bottom
})
}
})
})
})
.mouseup(Gui.clearMousemove)
//focus
el.panel.mousedown(function(){
$(this).css({zIndex:10})
.addClass('focus')
.removeClass('unfocus')
.siblings(selector)
.css({zIndex:9})
.addClass('unfocus')
.removeClass('focus')
})
Gui.close = (spec)=>{
spec.element.remove()
}
Gui.restore = (spec)=>{
TweenMax.to(spec.element,.1,{
left:spec.data.position.left,
right:spec.data.position.right,
top:spec.data.position.top,
bottom:spec.data.position.bottom
})
spec.data.state = 'natural'
$(spec.element).find('.window-title').addClass('hide')
}
Gui.maximize = (spec)=>{
return {
get expand(){
TweenMax.to(spec.element,.1,{
left:0,
right:0,
top:0,
bottom:40,
maxWidth:'initial'
})
spec.data.state = 'maximize'
},
get restore(){
Gui.restore(spec)
}
}
}
Gui.minimize = (spec)=>{
return {
get colapse(){
Gui.tray.push(spec.data)
TweenMax.to(spec.element,.1,{
left:minimizedSize*(Gui.tray.length-1),
right:$(window).width(),
top:$(window).height() - 40,
bottom:0
})
//heading
$(spec.element).find('.window-title').removeClass('hide')
spec.data.state = 'minimize'
$('.minimized').css({maxWidth:minimizedSize+'px'})
spec.element
.addClass('minimized')
},
get restore(){
Gui.tray.shift()
Gui.restore(spec)
spec.data.state = 'natural'
spec.element
.removeClass('minimized')
}
}
}
$('.window-ctrl > span').click(function(e){
let self = $(this).closest(el.panel),
current = Gui.getFrame({id:self.data('id')}),
requirements = {
element:self,
data:current
}
if($(this).hasClass('dismiss')){
Gui.close({element:self})
}
else if($(this).hasClass('min')){
Gui.minimize(requirements).colapse
}
else if($(this).hasClass('max')){
if(current.state==='natural'){
Gui.maximize(requirements).expand
}
else if(current.state==='maximize'){
Gui.maximize(requirements).restore
}
}
})
<script src="//cdnjs.cloudflare.com/ajax/libs/jquery/2.2.2/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/1.18.2/TweenMax.min.js"></script>
body
overflow:hidden;
width:100%;
min-height:100vh;
background-color:#f1f1f1;
background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAOEAAADhCAMAAAAJbSJIAAAAMFBMVEU0mNsyl9tRpeAzmNtTpuA7m9xFn91Jot5And1Cnt1pseRKod5bquFhreJWqOBjruIMsMgkAAACaUlEQVR4nO3d227bMAwAUN9yay79/79d2nSiHzS4zdCtoo6eiECMcSDIJkrVGYb3MS7j8DHGaSOcq+Gw+rR82Xw8l3B3KBNW4RBz96e5pJ1qaXGJ8bD7PXfcH0vabh9zV2mEhISEhH8tHB9jGcuYqmFMmA+1cFyFkXa/ZgnP1bSYezqW8LifK2mrueddCfcR7iJtKJcYlsd4WcrYCKevhJdrCW+vMeG1Mne5XLbSYu71NpWwmhaXaGoNI+0ra5h/HxISEhISEn5z1TZsP/Frj+4nn/jzc1Xb9BgvUxlb4b142gpLdLuWT+/lVyWM732r2kpYTYtLvFVtJYy01dWWktbUGqraer3TEBISEhKq2lRtqrYM+5CQkJCQkDBXh/S5J/7n2qL/pkM6baT9oUNaS9MhTbUPCQkJCQkJVW2qtgjbXcP8+5CQkJCQkDBXh/S5J/4n26I6pD94DfPvQ0JCQkJCQlWbqi3Cdtcw/z4kJCQkJCTUIdUhjbDdNcy/DwkJCQkJCVVtqrYI213D/PuQkJCQkJBQhzRVh3Rqv0P63Ntb8u9DQkJCQkLCXFXbt76vbfkRVVuUaknPtanaer3TEBISEhL21SH1i1btd0hVbb3eaQgJCQkJ+6ra2j/XtpWmalO1ERISEhIS6pDW2qKNnWtL0CH13wi93mkICQkJCVVtzrW1VbX5W1uvdxpCQkJCwr46pM616ZBm3YeEhISEhISqNlVbhO2uYf59SEhISEhIqEOa6lxbgve1OdfW652GkJCQkLCvqs1bdlVtWfchISEhISFhrg5p+2/+8ItWqrZe7zSEhIT/XfgLXg8P0fI5wpkAAAAASUVORK5CYII=");
&.dragging
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
cursor:move;
.panel
z-index:1;
border-bottom-left-radius:0;
border-bottom-right-radius:@border-bottom-left-radius;
min-width:200px;
min-height:40px;
margin:auto;
background-color:#dedede;
border:1px solid #9c9c9c;
position:absolute;
margin:0;
display:inline-block;
box-shadow: 0px 0px 18px 0px rgba(0,0,0,0.35);
transition:box-shadow .2s;
&.minimized
box-shadow:initial!important;
border-radius:0;
.window-ctrl
opacity:0;
pointer-events:none;
width:1px;
.panel-heading
border-radius:@border-radius;
.window-title
position:absolute;
top:0;
left:0;
right:0;
margin-top:.4em;
text-align:center;
color:#999;
font-size:1.2em;
text-shadow:-1px -1px #fff;
&.focus
box-shadow: 0px 0px 30px 0px rgba(0,0,0,0.45);
.panel-body
padding:15px 3px;
.inner
z-index:0;
padding-top:40px;
height:100%;
width:100%;
position:relative;
overflow:hidden;
.panel-heading
border-bottom:1px solid #9c9c9c;
background-color:#dedede;
color:#222222;
position:absolute;
width:100%;
z-index:1;
box-shadow:inset 0 1px 0 0 #fefefe, inset 1px 0 0 #f2f2f2, inset -1px 0 0 #f2f2f2, inset 0 -1px 0 0 #e6e6e6;
.window-ctrl
.glyphicon
font-weight:100;
color:#909090;
padding-left:10px;
padding-right:@padding-right;
transform:scale(.8);
&:first-child
padding-left:0;
&:last-child
padding-right:0;
.list-group-item
background-color:#fafafa;
border:#9c9c9c 1px solid;
color:#333;
border-radius:0;
border-top:0;
&:nth-child(even)
background-color:#f1f1f1;
&:first-child
border-top:@border;
.frame
$frame-hotspot = 10px
$correct-offset = -5px
$hotspot-color = rgba(255,255,255,0)
background-color:$hotspot-color;
position:absolute;
&.t
top:$correct-offset;
cursor:s-resize;
&.b
bottom:$correct-offset;
cursor:n-resize;
&.t,
&.b
width:100%;
height:$frame-hotspot;
left:$correct-offset;
&.l
left:$correct-offset;
&.r
right:$correct-offset;
&.l,
&.r
cursor:w-resize;
height:100%;
width:$frame-hotspot;
top:$correct-offset;
&.tl,
&.tr,
&.bl,
&.br
width:$frame-hotspot;
height:$frame-hotspot;
background-color:$hotspot-color;
z-index:2;
&.tl
top:$correct-offset;
left:$correct-offset;
cursor:se-resize;
&.tr
top:$correct-offset;
right:$correct-offset;
cursor:sw-resize;
&.bl
bottom:$correct-offset;
left:$correct-offset;
cursor:ne-resize;
&.br
bottom:$correct-offset;
right:$correct-offset;
cursor:nw-resize;
.snap
background-color:transparntify(blue,2%);
position:absolute;
.tray
height:40px;
width:100%;
position:absolute;
background-color:#dedede;
bottom:0;
z-index:0;
box-shadow:inset 0 1px 0 0 #fefefe, inset 1px 0 0 #f2f2f2, inset -1px 0 0 #f2f2f2, inset 0 -1px 0 0 #e6e6e6;
border-top:1px solid #9c9c9c;
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" rel="stylesheet" />

Window manager: Min, max, close, resize all sides, drag and drop, focus

  • Resize the div from any side including corners
  • Remembers size for Min Max

Plans:

  • Window snapping
  • Snap in to menu (Adobe like)
  • Remove Jquery dependencies
  • Remove Bootstrap dependencies
  • Build V2 with Api decent
  • Pure JS Implementation no html or css markup required
  • More configuration
  • Themes
  • Local storage memory
  • Componentized monetization.

A Pen by dimabelogurov on CodePen.

License.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment