Last active
November 19, 2020 07:01
-
-
Save mockee/b22bbc87ea85a6bd987a to your computer and use it in GitHub Desktop.
Facebook Puzzle — Canlendar
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!doctype html> | |
<html lang="en"> | |
<head> | |
<meta charset="utf-8"> | |
<title>Puzzle — Lay Out Day</title> | |
<style> | |
html,body,div,h3,p { margin: 0; padding: 0; } | |
body { | |
font: 12px 'lucida grande', tahoma, verdana, arial, sans-serif; | |
} | |
.calendar { | |
position: relative; | |
margin: 20px; | |
} | |
.times-range { | |
position: absolute; | |
top: -8px; | |
left: 0; | |
width: 76px; | |
color: #313131; | |
text-align: right; | |
vertical-align: top; | |
} | |
.times-range .time { | |
display: block; | |
color: #959595; | |
height: 30px; | |
font-size: 10px; | |
} | |
.times-range .time b { | |
font-size: 12px; | |
color: #585858; | |
margin-right: 5px; | |
} | |
.timeline { | |
position: relative; | |
width: 620px; | |
height: 720px; | |
padding: 0 10px; | |
margin-left: 85px; | |
margin-top: 8px; | |
-moz-box-sizing: border-box; | |
box-sizing: border-box; | |
background: #ececec; | |
border-left: 1px solid #d9d9d9; | |
} | |
.event-item { | |
position: absolute; | |
left: 0; | |
top: 0; | |
cursor: pointer; | |
border-width: 1px 1px 1px 4px; | |
border-style: solid; | |
border-color: #d5d5d5 #d5d5d5 #d5d5d5 #5173aa; | |
background: #fff; | |
-moz-box-sizing: border-box; | |
box-sizing: border-box; | |
overflow: hidden; | |
word-wrap: break-word; | |
} | |
.event-item:hover { | |
background: #eff2f7; | |
} | |
.event-item .title { | |
font-weight: bold; | |
font-size: 13px; | |
color: #5173aa; | |
margin: 8px 0 0 8px; | |
} | |
.event-item .location { | |
font-size: 11px; | |
color: #8b8b8b; | |
margin: 0 0 0 8px; | |
} | |
.event-item .title, | |
.event-item .location { | |
white-space: nowrap; | |
overflow: hidden; | |
text-overflow: ellipsis; | |
} | |
</style> | |
<script> | |
// Creating `time` element for IE8. | |
;(function(doc) { doc.createElement('time') }(document)) | |
</script> | |
</head> | |
<body> | |
<div class="calendar"> | |
<div class="times-range"></div> | |
<div class="timeline"></div> | |
</div> | |
</body> | |
<script id="tmpl-time-range" type="text/template"> | |
<time class="time">{%= time %}</time> | |
</script> | |
<script id="tmpl-event-item" type="text/template"> | |
<div class="event-item" | |
style="width:{%= width %};height:{%= height %};top:{%= top %};left:{%= left %};"> | |
<h3 class="title">Event Item</h3> | |
<p class="location">Location</p> | |
</div> | |
</script> | |
<script> | |
;(function(exports, doc) { | |
"use strict"; | |
// `isArray`, `every`, `reduce` `filter` for IE8 | |
var isObject = function(obj) { | |
return obj === Object(obj) | |
} | |
, isArray = Array.isArray || function(obj) { | |
return ({}).toString.call(obj) === '[object Array]' | |
} | |
, every = function(obj, iterator, context) { | |
if (Array.prototype.every) { | |
return obj.every(iterator, context) | |
} | |
for (var i = 0, l = obj.length; i < l; i++) { | |
if (i in obj && !iterator.call(context, obj[i], i, obj)) { | |
return false | |
} | |
} | |
return true | |
} | |
, reduce = function(obj, iterator) { | |
var hasInitValue = false | |
, value | |
if (arguments.length > 2) { | |
hasInitValue = true | |
value = arguments[2] | |
} | |
if (Array.prototype.reduce) { | |
return hasInitValue | |
? obj.reduce(iterator, value) | |
: obj.reduce(iterator) | |
} | |
for (var i = 0, l = obj.length; i < l; i++) { | |
if (hasInitValue) { | |
value = iterator(value, obj[i], i, obj) | |
} else { | |
value = obj[i] | |
hasInitValue = true | |
} | |
} | |
return value | |
} | |
, filter = function(obj, iterator, context) { | |
var results = [] | |
if (Array.prototype.filter) { | |
return obj.filter(iterator, context) | |
} | |
for (var i = 0, l = obj.length; i < l; i++) { | |
if (iterator.call(context, obj[i], i, obj)) { | |
results.push(obj[i]) | |
} | |
} | |
return results | |
} | |
, hasOwnProp = function(obj, key) { | |
return ({}).hasOwnProperty.call(obj, key) | |
} | |
, isEmpty = function(obj) { | |
if (obj === null) { return true } | |
if (isArray(obj)) { return obj.length === 0 } | |
for (var key in obj) { | |
if (hasOwnProp(obj, key)) { return false } | |
} | |
return true | |
} | |
// Micro-template | |
, cache = {} | |
, template = function(str, data) { | |
var fn = cache[str] = cache[str] || | |
new Function("obj", "var p=[];with(obj){p.push('" + | |
str.replace(/[\r\t\n]/g, " ") | |
.replace(/'(?=[^%]*%})/g,"\t") | |
.split("'").join("\\'") | |
.split("\t").join("'") | |
.replace(/{%=(.+?)%}/g, "',$1,'") | |
.split("{%").join("');") | |
.split("%}").join("p.push('") | |
+ "');}return p.join('');") | |
return fn(data) | |
} | |
function _renderTimesRange() { | |
var hourTime = 12 | |
, initTime = 9 | |
, time, clock, minute | |
, timesStrings = '' | |
, isAfterMoon | |
, isEvenItem | |
, elTimesRange = doc.querySelector('.times-range') | |
, tmplRange = doc.getElementById('tmpl-time-range').innerHTML | |
for (var i = 0; i <= 24; i++) { | |
isEvenItem = i % 2 === 0 | |
clock = initTime += (i !== 0 && isEvenItem ? 1 : 0) | |
isAfterMoon = clock > hourTime | |
clock = isAfterMoon ? clock - hourTime : clock | |
minute = ':' + (isEvenItem ? '00' : '30') | |
// hh:mm AM / PM | |
time = isEvenItem ? '<b>' + clock + minute + '</b>' | |
+ (isAfterMoon ? 'PM' : 'AM') : clock + minute | |
timesStrings += template(tmplRange, { time: time }) | |
} | |
elTimesRange.innerHTML = timesStrings | |
} | |
function renderEventItems(data) { | |
var tmplEventItem = doc.getElementById('tmpl-event-item').innerHTML | |
, elTimeline = doc.querySelector('.timeline') | |
, timelineString = '' | |
, eventGroup | |
for (var i = 0, l = data.length; i < l; i++) { | |
eventGroup = data[i] | |
eventGroup.maxOrder = | |
eventGroup.sort(function(a, b) { | |
return b.order - a.order | |
})[0].order | |
for (var j = 0, k = eventGroup.length; j < k; j++) { | |
var item = eventGroup[j] | |
, itemWidth = 599 / eventGroup.maxOrder | |
, itemTop = item.start < 0 ? 0 : item.start | |
, itemHeight = (item.end > 720 ? 720 : item.end) - itemTop | |
timelineString += template(tmplEventItem, { | |
width: itemWidth + 'px' | |
, height: itemHeight + 'px' | |
, left: 10 + (item.order - 1) * itemWidth + 'px' | |
, top: itemTop + 'px' | |
}) | |
} | |
} | |
elTimeline.innerHTML = timelineString | |
} | |
function dataTransform(events) { | |
var initSingleEvt = function(evt) { | |
evt.order = 1 | |
evt.point = evt.end | |
return evt | |
} | |
if (events.length === 1) { | |
return [[initSingleEvt(events[0])]] | |
} | |
var result = [] | |
, lastEvent = {} | |
, eventsIdx = events.length - 1 | |
events = events.sort(function(a, b) { | |
return a.start - b.start | |
}) | |
reduce(events, function(prev, curr, idx) { | |
if (!isArray(prev)) { | |
prev = [initSingleEvt(prev)] | |
} | |
lastEvent = prev[prev.length - 1] | |
if (curr.start < lastEvent.point) { | |
curr.point = Math.min(lastEvent.point, curr.end) | |
curr.order = prev.sort(function(a, b){ | |
return b.order - a.order | |
})[0].order + 1 | |
prev.push(curr) | |
if (idx === eventsIdx) { | |
result.push(prev) | |
} | |
return prev | |
} else if (curr.start < lastEvent.end) { | |
var subEvents = filter(prev, function(v) { | |
return v.end < curr.start | |
}) | |
curr.point = lastEvent.end | |
curr.order = subEvents.length ? | |
subEvents.sort(function(a, b) { | |
return b.order - a.order | |
})[0].order : 1 | |
prev.push(curr) | |
if (idx === eventsIdx) { | |
result.push(prev) | |
} | |
return prev | |
} else { | |
var subEvents = filter(prev, function(v) { | |
return v.end > curr.start | |
}) | |
if (subEvents.length) { | |
var near = subEvents[0] | |
curr.point = Math.min(near.point, curr.end) | |
curr.order = near.order + 1 | |
prev.push(curr) | |
if (idx === eventsIdx) { | |
result.push(prev) | |
} | |
return prev | |
} else { | |
if (prev) { result.push(prev) } | |
if (idx === eventsIdx) { | |
result.push([initSingleEvt(curr)]) | |
} | |
return curr | |
} | |
} | |
}) | |
return result | |
} | |
/** | |
* @public Lay out events day | |
* @param {array} events list or {object} single event | |
* | |
* @note | |
* layoutday([ | |
* { start: 30, end: 150 } | |
* , { start: 540, end:600 } | |
* ]) | |
* layoutday({ start: 30, end: 150 }) | |
* | |
*/ | |
function layOutDay(events) { | |
if (isEmpty(events)) { | |
throw Error('No event here.') | |
} | |
if (isObject(events) && !isArray(events)) { | |
events = [events] | |
} | |
var isTimesValid = every(events, function(item) { | |
return item.end > item.start | |
}) | |
if (!isTimesValid) { | |
throw Error('End value must greater than start.') | |
} | |
var data = dataTransform(events) | |
renderEventItems(data) | |
} | |
_renderTimesRange() | |
exports.layOutDay = layOutDay | |
}(window, document)) | |
// Init default events | |
;(function(win) { | |
var defaultEvents = [ | |
{ start: 30, end: 150 } | |
, { start: 540, end: 600 } | |
, { start: 560, end: 620 } | |
, { start: 610, end: 670 } | |
] | |
win.layOutDay(defaultEvents) | |
}(window)) | |
</script> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment