Add calendar live editing

This commit is contained in:
Lars Westermann 2019-05-10 11:59:41 +02:00
parent 8063e15421
commit 4e5dc610a3
Signed by: lars.westermann
GPG key ID: 9D417FA5BB9D5E1D
35 changed files with 2239 additions and 111 deletions

View file

@ -1,19 +1,381 @@
package de.kif.frontend
import de.kif.frontend.calendar.Calendar
import de.westermann.kwebview.components.boxView
import de.westermann.kwebview.components.h1
import de.westermann.kobserve.property.mapBinding
import de.westermann.kwebview.*
import de.westermann.kwebview.components.Body
import de.westermann.kwebview.components.init
import kif.common.model.*
import org.w3c.dom.*
import org.w3c.dom.events.EventListener
import org.w3c.dom.events.MouseEvent
import kotlin.browser.document
import kotlin.browser.window
import kotlin.collections.Iterator
import kotlin.collections.List
import kotlin.collections.emptyList
import kotlin.collections.filter
import kotlin.collections.find
import kotlin.collections.forEach
import kotlin.collections.listOf
import kotlin.collections.minus
import kotlin.collections.plus
import kotlin.dom.appendText
fun main() = init {
clear()
h1("Test")
boxView {
style {
width = "600px"
height = "400px"
margin = "10px"
class CalendarTools(entry: CalendarEntry, view: HTMLElement) : View(view) {
init {
var linkM10: HTMLAnchorElement? = null
var linkM5: HTMLAnchorElement? = null
var linkReset: HTMLAnchorElement? = null
var linkP5: HTMLAnchorElement? = null
var linkP10: HTMLAnchorElement? = null
var linkDel: HTMLAnchorElement? = null
for (element in html.children) {
when {
element.classList.contains("calendar-tools-m10") -> linkM10 = element as? HTMLAnchorElement
element.classList.contains("calendar-tools-m5") -> linkM5 = element as? HTMLAnchorElement
element.classList.contains("calendar-tools-reset") -> linkReset = element as? HTMLAnchorElement
element.classList.contains("calendar-tools-p5") -> linkP5 = element as? HTMLAnchorElement
element.classList.contains("calendar-tools-p10") -> linkP10 = element as? HTMLAnchorElement
element.classList.contains("calendar-tools-del") -> linkDel = element as? HTMLAnchorElement
}
}
+Calendar()
linkM10 = linkM10 ?: run {
val link = createHtmlView<HTMLAnchorElement>()
link.classList.add("calendar-tools-m10")
link.textContent = "-10"
html.appendChild(link)
link
}
linkM10.removeAttribute("href")
linkM10.addEventListener("click", EventListener {
classList += "pending"
get("/calendar/${entry.day}/${entry.room}/${entry.time}/-1") {
get("/calendar/${entry.day}/${entry.room}/${entry.timeId - 10}/${entry.workGroup}") {
println("success")
}
}
})
linkM5 = linkM5 ?: run {
val link = createHtmlView<HTMLAnchorElement>()
link.classList.add("calendar-tools-m5")
link.textContent = "-5"
html.appendChild(link)
link
}
linkM5.removeAttribute("href")
linkM5.addEventListener("click", EventListener {
classList += "pending"
get("/calendar/${entry.day}/${entry.room}/${entry.time}/-1") {
get("/calendar/${entry.day}/${entry.room}/${entry.timeId - 5}/${entry.workGroup}") {
println("success")
}
}
})
linkReset = linkReset ?: run {
val link = createHtmlView<HTMLAnchorElement>()
link.classList.add("calendar-tools-reset")
link.textContent = "reset"
html.appendChild(link)
link
}
linkReset.removeAttribute("href")
linkReset.addEventListener("click", EventListener {
classList += "pending"
get("/calendar/${entry.day}/${entry.room}/${entry.time}/-1") {
get("/calendar/${entry.day}/${entry.room}/${entry.cellTime}/${entry.workGroup}") {
println("success")
}
}
})
linkP5 = linkP5 ?: run {
val link = createHtmlView<HTMLAnchorElement>()
link.classList.add("calendar-tools-p5")
link.textContent = "+5"
html.appendChild(link)
link
}
linkP5.removeAttribute("href")
linkP5.addEventListener("click", EventListener {
classList += "pending"
get("/calendar/${entry.day}/${entry.room}/${entry.time}/-1") {
get("/calendar/${entry.day}/${entry.room}/${entry.timeId + 5}/${entry.workGroup}") {
println("success")
}
}
})
linkP10 = linkP10 ?: run {
val link = createHtmlView<HTMLAnchorElement>()
link.classList.add("calendar-tools-p10")
link.textContent = "+10"
html.appendChild(link)
link
}
linkP10.removeAttribute("href")
linkP10.addEventListener("click", EventListener {
classList += "pending"
get("/calendar/${entry.day}/${entry.room}/${entry.time}/-1") {
get("/calendar/${entry.day}/${entry.room}/${entry.timeId + 10}/${entry.workGroup}") {
println("success")
}
}
})
linkDel = linkDel ?: run {
val link = createHtmlView<HTMLAnchorElement>()
link.classList.add("calendar-tools-del")
link.textContent = "del"
html.appendChild(link)
link
}
linkDel.removeAttribute("href")
linkDel.addEventListener("click", EventListener {
classList += "pending"
get("/calendar/${entry.day}/${entry.room}/${entry.time}/-1") {
println("success")
}
})
}
}
class CalendarEntry(view: HTMLElement) : View(view) {
private lateinit var mouseDelta: Point
private var newCell: CalendarCell? = null
var day by dataset.property("day")
val dayId by dataset.property("day").mapBinding { it?.toIntOrNull() ?: 0 }
var time by dataset.property("time")
val timeId by dataset.property("time").mapBinding { it?.toIntOrNull() ?: 0 }
var room by dataset.property("room")
val roomId by dataset.property("room").mapBinding { it?.toIntOrNull() ?: 0 }
var cellTime by dataset.property("cellTime")
var language by dataset.property("language")
var workGroup by dataset.property("workgroup")
val workGroupId by dataset.property("workgroup").mapBinding { it?.toIntOrNull() ?: 0 }
private fun onMove(event: MouseEvent) {
val position = event.toPoint() - mouseDelta
newCell?.classList?.remove("drag")
val cell = calendarCells.find {
position in it.dimension
}
if (cell != null) {
cell.classList.add("drag")
cell += this
newCell = cell
}
event.preventDefault()
event.stopPropagation()
}
private fun onFinishMove(event: MouseEvent) {
classList -= "drag"
newCell?.let { cell ->
cell.classList -= "drag"
val newTime = cell.time
val newRoom = cell.room
val day =
(document.getElementsByClassName("calendar")[0] as? HTMLElement)?.dataset?.get("day")?.toIntOrNull()
?: 0
classList += "pending"
get("/calendar/$day/$room/$time/-1") {
get("/calendar/$day/$newRoom/$newTime/$workGroup") {
println("success")
}
}
}
newCell = null
for (it in listeners) {
it.detach()
}
listeners = emptyList()
event.preventDefault()
event.stopPropagation()
}
private var listeners: List<de.westermann.kobserve.event.EventListener<*>> = emptyList()
init {
onMouseDown { event ->
if (event.target != html || "pending" in classList) {
event.stopPropagation()
return@onMouseDown
}
classList += "drag"
mouseDelta = event.toPoint() - point
listeners = listOf(
Body.onMouseMove.reference(this::onMove),
Body.onMouseUp.reference(this::onFinishMove),
Body.onMouseLeave.reference(this::onFinishMove)
)
event.preventDefault()
event.stopPropagation()
}
var calendarTools: CalendarTools? = null
for (item in html.children) {
if (item.classList.contains("calendar-tools")) {
calendarTools = CalendarTools(this, item)
break
}
}
if (calendarTools == null) {
calendarTools = CalendarTools(this, createHtmlView())
html.appendChild(calendarTools.html)
}
}
companion object {
fun create(
day: Int,
time: Int,
cellTime: Int,
room: Int,
workGroupId: Int,
workGroupName: String,
workGroupLength: Int,
workGroupLanguage: String,
workGroupColor: Color?
): CalendarEntry {
val entry = CalendarEntry(createHtmlView())
entry.day = day.toString()
entry.time = time.toString()
entry.cellTime = cellTime.toString()
entry.room = room.toString()
entry.workGroup = workGroupId.toString()
entry.language = workGroupLanguage
if (workGroupColor != null) {
entry.style {
val size = workGroupLength / CALENDAR_GRID_WIDTH.toDouble()
val pos = (time % CALENDAR_GRID_WIDTH) / CALENDAR_GRID_WIDTH.toDouble()
val pct = "${pos * 100}%"
val sz = "${size * 100}%"
left = pct
top = "calc($pct + 0.1rem)"
width = sz
height = "calc($sz - 0.2rem)"
backgroundColor = workGroupColor.toString()
color = workGroupColor.textColor.toString()
}
}
entry.html.appendText(workGroupName)
return entry
}
}
}
class CalendarCell(view: HTMLElement) : ViewCollection<CalendarEntry>(view) {
var day by dataset.property("day")
val dayId by dataset.property("day").mapBinding { it?.toIntOrNull() ?: 0 }
var time by dataset.property("time")
val timeId by dataset.property("time").mapBinding { it?.toIntOrNull() ?: 0 }
var room by dataset.property("room")
val roomId by dataset.property("room").mapBinding { it?.toIntOrNull() ?: 0 }
}
var calendarEntries: List<CalendarEntry> = emptyList()
var calendarCells: List<CalendarCell> = emptyList()
fun main() = init {
val ws = WebSocket("ws://${window.location.host}/".also { println(it) })
ws.onmessage = {
val messageWrapper = Message.parse(it.data?.toString() ?: "")
val message = messageWrapper.message
println(message)
when (message) {
is MessageCreateCalendarEntry -> {
val entry = CalendarEntry.create(
message.day,
message.time,
message.cellTime,
message.room,
message.workGroupId,
message.workGroupName,
message.workGroupLength,
message.workGroupLanguage,
message.workGroupColor
)
for (cell in calendarCells) {
if (cell.dayId == message.day && cell.timeId == message.cellTime && cell.roomId == message.room) {
cell.html.appendChild(entry.html)
calendarEntries += entry
break
}
}
}
is MessageDeleteCalendarEntry -> {
val remove = calendarEntries.filter { entry ->
entry.dayId == message.day &&
entry.timeId == message.time &&
entry.roomId == message.roomId &&
entry.workGroupId == message.workGroupId
}
calendarEntries -= remove
remove.forEach {
it.html.remove()
}
}
else -> {
}
}
}
ws.onopen = {
console.log("yes!")
}
calendarEntries = document.getElementsByClassName("calendar-entry")
.iterator().asSequence().map(::CalendarEntry).toList()
calendarCells = document.getElementsByClassName("calendar-cell")
.iterator().asSequence().filter { it.dataset["time"] != null }.map(::CalendarCell).toList()
}
operator fun HTMLCollection.iterator() = object : Iterator<HTMLElement> {
private var index = 0
override fun hasNext(): Boolean {
return index < this@iterator.length
}
override fun next(): HTMLElement {
return this@iterator.get(index++) as HTMLElement
}
}

View file

@ -1,7 +1,7 @@
package de.westermann.kwebview
import de.westermann.kobserve.Property
import de.westermann.kobserve.basic.property
import de.westermann.kobserve.property.property
import kotlin.reflect.KProperty
/**

View file

@ -1,6 +1,6 @@
package de.westermann.kwebview
import de.westermann.kobserve.ListenerReference
import de.westermann.kobserve.event.EventListener
import de.westermann.kobserve.Property
import de.westermann.kobserve.ReadOnlyProperty
import org.w3c.dom.DOMTokenList
@ -102,7 +102,7 @@ class ClassList(
throw IllegalArgumentException("Class is not bound!")
}
bound[clazz]?.reference?.remove()
bound[clazz]?.reference?.detach()
bound -= clazz
}
@ -114,6 +114,6 @@ class ClassList(
private data class Bound(
val property: ReadOnlyProperty<Boolean>,
val reference: ListenerReference<Unit>?
val reference: EventListener<Unit>?
)
}

View file

@ -1,11 +1,16 @@
package de.westermann.kwebview
import de.westermann.kobserve.ListenerReference
import de.westermann.kobserve.Property
import de.westermann.kobserve.ReadOnlyProperty
import de.westermann.kobserve.event.EventListener
import org.w3c.dom.DOMStringMap
import org.w3c.dom.get
import org.w3c.dom.set
import kotlin.collections.MutableMap
import kotlin.collections.contains
import kotlin.collections.minusAssign
import kotlin.collections.mutableMapOf
import kotlin.collections.set
/**
* Represents the css classes of an html element.
@ -13,7 +18,7 @@ import org.w3c.dom.set
* @author lars
*/
class DataSet(
private val map: DOMStringMap
private val map: DOMStringMap
) {
private val bound: MutableMap<String, Bound> = mutableMapOf()
@ -49,11 +54,11 @@ class DataSet(
* Set css class present.
*/
operator fun set(key: String, value: String?) =
if (value == null) {
this -= key
} else {
this += key to value
}
if (value == null) {
this -= key
} else {
this += key to value
}
fun bind(key: String, property: ReadOnlyProperty<String>) {
if (key in bound) {
@ -71,22 +76,34 @@ class DataSet(
bound[key] = Bound(key, property, null)
}
fun property(key: String): Property<String?> {
if (key in bound) {
return bound[key]?.propertyNullable as? Property<String?> ?: throw IllegalArgumentException("Class is already bound!")
}
val property = de.westermann.kobserve.property.property(get(key))
bound[key] = Bound(key, property, null)
return property
}
fun unbind(key: String) {
if (key !in bound) {
throw IllegalArgumentException("Class is not bound!")
}
bound[key]?.reference?.remove()
bound[key]?.reference?.detach()
bound -= key
}
private inner class Bound(
val key: String,
val propertyNullable: ReadOnlyProperty<String?>?,
val property: ReadOnlyProperty<String>?
val key: String,
val propertyNullable: ReadOnlyProperty<String?>?,
val property: ReadOnlyProperty<String>?
) {
var reference: ListenerReference<Unit>? = null
var reference: EventListener<Unit>? = null
fun set(value: String?) {
if (propertyNullable != null && propertyNullable is Property) {

View file

@ -1,6 +1,7 @@
package de.westermann.kwebview
import de.westermann.kobserve.EventHandler
import de.westermann.kobserve.event.EventHandler
import org.w3c.dom.DragEvent
import org.w3c.dom.HTMLElement
import org.w3c.dom.css.CSSStyleDeclaration
import org.w3c.dom.events.FocusEvent
@ -63,6 +64,9 @@ abstract class View(view: HTMLElement = createHtmlView()) {
val dimension: Dimension
get() = html.getBoundingClientRect().toDimension()
val point: Point
get() = dimension.position
var title by AttributeDelegate()
val style = view.style
@ -101,6 +105,15 @@ abstract class View(view: HTMLElement = createHtmlView()) {
val onFocus = EventHandler<FocusEvent>()
val onBlur = EventHandler<FocusEvent>()
val onDragStart = EventHandler<DragEvent>()
val onDrag = EventHandler<DragEvent>()
val onDragEnter = EventHandler<DragEvent>()
val onDragLeave = EventHandler<DragEvent>()
val onDragOver = EventHandler<DragEvent>()
val onDrop = EventHandler<DragEvent>()
val onDragEnd = EventHandler<DragEvent>()
init {
onClick.bind(view, "click")
onDblClick.bind(view, "dblclick")
@ -120,5 +133,17 @@ abstract class View(view: HTMLElement = createHtmlView()) {
onFocus.bind(view, "focus")
onBlur.bind(view, "blur")
onDragStart.bind(view, "dragstart")
onDrag.bind(view, "drag")
onDragEnter.bind(view, "dragenter")
onDragLeave.bind(view, "dragleave")
onDragOver.bind(view, "dragover")
onDrop.bind(view, "drop")
onDragEnd.bind(view, "dragend")
}
companion object {
fun wrap(htmlElement: HTMLElement) = object : View(htmlElement) {}
}
}

View file

@ -66,4 +66,8 @@ abstract class ViewCollection<V : View>(view: HTMLElement = createHtmlView()) :
operator fun V.unaryPlus() {
append(this)
}
companion object {
fun <V : View> wrap(htmlElement: HTMLElement) = object : ViewCollection<V>(htmlElement) {}
}
}

View file

@ -2,7 +2,7 @@ package de.westermann.kwebview.components
import de.westermann.kobserve.Property
import de.westermann.kobserve.ReadOnlyProperty
import de.westermann.kobserve.basic.property
import de.westermann.kobserve.property.property
import de.westermann.kwebview.KWebViewDsl
import de.westermann.kwebview.View
import de.westermann.kwebview.ViewCollection

View file

@ -3,7 +3,7 @@ package de.westermann.kwebview.components
import de.westermann.kobserve.Property
import de.westermann.kobserve.ReadOnlyProperty
import de.westermann.kobserve.ValidationProperty
import de.westermann.kobserve.basic.property
import de.westermann.kobserve.property.property
import de.westermann.kwebview.*
import org.w3c.dom.events.Event
import org.w3c.dom.events.EventListener

View file

@ -2,7 +2,7 @@ package de.westermann.kwebview.components
import de.westermann.kobserve.Property
import de.westermann.kobserve.ReadOnlyProperty
import de.westermann.kobserve.basic.property
import de.westermann.kobserve.property.property
import de.westermann.kwebview.KWebViewDsl
import de.westermann.kwebview.View
import de.westermann.kwebview.ViewCollection

View file

@ -2,7 +2,7 @@ package de.westermann.kwebview.components
import de.westermann.kobserve.Property
import de.westermann.kobserve.ReadOnlyProperty
import de.westermann.kobserve.basic.property
import de.westermann.kobserve.property.property
import de.westermann.kwebview.*
import org.w3c.dom.HTMLImageElement

View file

@ -3,7 +3,7 @@ package de.westermann.kwebview.components
import de.westermann.kobserve.Property
import de.westermann.kobserve.ReadOnlyProperty
import de.westermann.kobserve.ValidationProperty
import de.westermann.kobserve.basic.property
import de.westermann.kobserve.property.property
import de.westermann.kobserve.not
import de.westermann.kwebview.*
import org.w3c.dom.HTMLInputElement

View file

@ -2,7 +2,7 @@ package de.westermann.kwebview.components
import de.westermann.kobserve.Property
import de.westermann.kobserve.ReadOnlyProperty
import de.westermann.kobserve.basic.property
import de.westermann.kobserve.property.property
import de.westermann.kwebview.*
import org.w3c.dom.HTMLLabelElement

View file

@ -2,7 +2,7 @@ package de.westermann.kwebview.components
import de.westermann.kobserve.Property
import de.westermann.kobserve.ReadOnlyProperty
import de.westermann.kobserve.basic.property
import de.westermann.kobserve.property.property
import de.westermann.kwebview.AttributeDelegate
import de.westermann.kwebview.KWebViewDsl
import de.westermann.kwebview.ViewCollection

View file

@ -2,7 +2,7 @@ package de.westermann.kwebview.components
import de.westermann.kobserve.Property
import de.westermann.kobserve.ReadOnlyProperty
import de.westermann.kobserve.basic.property
import de.westermann.kobserve.property.property
import de.westermann.kwebview.KWebViewDsl
import de.westermann.kwebview.View
import de.westermann.kwebview.ViewCollection

View file

@ -1,11 +1,13 @@
package de.westermann.kwebview
import de.westermann.kobserve.EventHandler
import de.westermann.kobserve.event.EventHandler
import org.w3c.dom.DOMRect
import org.w3c.dom.HTMLElement
import org.w3c.dom.events.Event
import org.w3c.dom.events.EventListener
import org.w3c.dom.events.MouseEvent
import org.w3c.xhr.FormData
import org.w3c.xhr.XMLHttpRequest
import kotlin.browser.document
import kotlin.browser.window
@ -15,9 +17,8 @@ inline fun <reified V : HTMLElement> createHtmlView(tag: String? = null): V {
tagName = tag
} else {
tagName = V::class.js.name.toLowerCase().replace("html([a-z]*)element".toRegex(), "$1")
if (tagName.isBlank()) {
tagName = "div"
}
if (tagName.isBlank()) tagName = "div"
if (tagName == "anchor") tagName = "a"
}
return document.createElement(tagName) as V
}
@ -77,3 +78,66 @@ fun interval(timeout: Int, block: () -> Unit): Int {
fun clearInterval(id: Int) {
window.clearInterval(id)
}
fun get(
url: String,
data: Map<String, String> = emptyMap(),
onError: (Int) -> Unit = {},
onSuccess: (String) -> Unit = {}
) {
val xhttp = XMLHttpRequest()
xhttp.onreadystatechange = {
if (xhttp.readyState == 4.toShort()) {
if (xhttp.status == 200.toShort() || xhttp.status == 304.toShort()) {
onSuccess(xhttp.responseText)
} else {
onError(xhttp.status.toInt())
}
}
}
xhttp.open("GET", url, true)
if (data.isNotEmpty()) {
xhttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
val formData = FormData()
for ((key, value) in data) {
formData.append(key, value)
}
xhttp.send(formData)
} else {
xhttp.send()
}
}
fun post(
url: String,
data: Map<String, String> = emptyMap(),
onError: (Int) -> Unit = {},
onSuccess: (String) -> Unit = {}
) {
val xhttp = XMLHttpRequest()
xhttp.onreadystatechange = {
if (xhttp.readyState == 4.toShort()) {
console.log(xhttp.status)
if (xhttp.status == 200.toShort() || xhttp.status == 304.toShort()) {
onSuccess(xhttp.responseText)
} else {
onError(xhttp.status.toInt())
}
}
}
xhttp.open("POST", url, true)
if (data.isNotEmpty()) {
xhttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
val formData = FormData()
for ((key, value) in data) {
formData.append(key, value)
}
xhttp.send(formData)
} else {
xhttp.send()
}
}

View file

@ -102,6 +102,7 @@ a {
position: relative;
text-transform: uppercase;
white-space: nowrap;
z-index: 6;
&::after {
content: '';
@ -157,13 +158,24 @@ a {
display: none;
position: absolute;
background-color: $background-secondary-color;
z-index: 1;
z-index: 5;
left: 1rem;
right: 1rem;
&::before {
content: '';
top: -8rem;
left: 0;
right: 0;
height: 8rem;
display: block;
position: absolute;
}
a {
display: block;
line-height: 3rem;
&:after {
bottom: 0.2rem;
}
@ -192,6 +204,7 @@ a {
a {
display: inline-block;
line-height: unset;
&:after {
bottom: 1.8em
}
@ -215,7 +228,7 @@ a {
.btn-search {
position: absolute;
top: 0;
height: calc(2.5rem + 2px);
height: 2.5rem;
line-height: 2.5rem;
right: -3px;
border-bottom-left-radius: 0;
@ -228,9 +241,6 @@ a {
input:focus ~ .btn-search {
border-color: $primary-color;
border-width: 2px;
margin-top: 0;
height: calc(2.5rem + 4px);
margin-right: 1px;
}
}
@ -282,6 +292,7 @@ a {
outline: none;
padding: 0 1rem;
line-height: 2.5rem;
height: 2.5rem;
width: 100%;
background-color: $background-primary-color;
border-radius: 0.2rem;
@ -291,10 +302,14 @@ a {
&:focus {
border-color: $primary-color;
border-width: 2px;
margin: 0;
}
}
select:-moz-focusring {
color: transparent;
text-shadow: 0 0 0 $text-primary-color;
}
.form-group {
padding-bottom: 1rem;
display: block;
@ -411,7 +426,7 @@ a {
border-color: $error-color;
}
button::-moz-focus-inner, input::-moz-focus-inner {
button::-moz-focus-inner, input::-moz-focus-inner, select::-moz-focus-inner {
border: 0;
}
@ -425,3 +440,358 @@ form {
width: 24rem;
}
}
.input-group {
display: flex;
.form-btn {
height: 2.5rem;
line-height: 2.5rem;
margin-top: 1px;
}
& > * {
margin-right: 0;
&:not(:first-child) {
border-top-left-radius: 0;
border-bottom-left-radius: 0;
margin-left: -1px;
}
&:not(:last-child) {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
}
}
}
.calendar {
width: 100%;
position: relative;
margin-top: 1rem;
& > div {
width: calc(100% - 6rem);
overflow-x: scroll;
overflow-y: visible;
margin-left: 6rem;
padding-bottom: 1rem;
}
}
.calendar-tools {
position: absolute;
top: -3rem;
right: 0;
background-color: #fff;
padding: 0.2rem 0.5rem;
border-radius: 0.2rem;
display: none;
z-index: 10;
border: solid 1px $border-color;
box-shadow: 0 0.1rem 0.2rem rgba($primary-text-color);
a {
padding: 0.2rem;
}
&::after {
content: '';
position: absolute;
left: 0;
right: 0;
bottom: -1rem;
height: 1rem;
}
}
.calendar-entry {
position: absolute;
display: block;
border-radius: 0.2rem;
z-index: 1;
line-height: 2rem;
font-size: 0.8rem;
white-space: nowrap;
text-overflow: ellipsis;
background-color: $primary-color;
color: $primary-text-color;
padding: 0 0.5rem;
&::after {
content: attr(data-language);
bottom: 0;
right: 1rem;
opacity: 0.6;
position: absolute;
text-transform: uppercase;
}
&:hover .calendar-tools {
display: block;
}
&.drag {
box-shadow: 0 0.1rem 0.2rem rgba($text-primary-color, 0.8);
z-index: 2;
.calendar-tools {
display: none;
}
}
&.pending::before {
content: '';
display: block;
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
background-color: $background-primary-color;
opacity: 0.6;
}
}
.calendar-table-time-to-room {
.calendar-header, .calendar-row {
display: flex;
flex-wrap: nowrap;
width: max-content;
}
.calendar-header {
line-height: 2rem;
height: 2rem;
.calendar-cell:not(:first-child) {
font-size: 0.8rem;
width: 6rem;
position: relative;
padding-left: 0.2rem;
&::before {
content: '';
height: 100%;
left: 0;
top: 0;
border-left: solid 1px rgba($text-primary-color, 0.1);
position: absolute;
}
}
}
.calendar-row {
border-top: solid 1px rgba($text-primary-color, 0.1);
line-height: 3rem;
height: 3rem;
.calendar-cell {
position: relative;
width: 1.5rem;
&:nth-child(2n + 2)::before {
content: '';
height: 100%;
left: 0;
top: 0;
border-left: solid 1px rgba($text-primary-color, 0.1);
position: absolute;
}
&:hover {
background-color: rgba($text-primary-color, 0.06);
}
.calendar-entry {
top: 0.5rem !important;
bottom: 0.5rem;
width: 0;
left: 0;
height: auto !important;
}
}
}
.calendar-cell:first-child {
position: absolute;
width: 6rem;
left: 0;
text-align: center;
border-right: solid 1px rgba($text-primary-color, 0.1);
}
.calendar-link {
display: block;
width: 100%;
height: 100%;
}
}
.calendar-table-room-to-time {
.calendar-header, .calendar-row {
display: flex;
flex-wrap: nowrap;
width: max-content;
}
.calendar-header {
line-height: 2rem;
height: 2rem;
.calendar-cell:not(:first-child) {
width: 12rem;
position: relative;
padding-left: 0.2rem;
&::before {
content: '';
height: 100%;
left: 0;
top: 0;
border-left: solid 1px rgba($text-primary-color, 0.1);
position: absolute;
}
}
}
.calendar-row {
line-height: 2rem;
height: 1.3rem;
&:nth-child(4n + 2)::before {
content: '';
position: absolute;
width: calc(100% - 6rem);
border-top: solid 1px rgba($text-primary-color, 0.1);
}
.calendar-cell {
position: relative;
width: 12rem;
&::before {
content: '';
height: 100%;
left: 0;
top: 0;
border-left: solid 1px rgba($text-primary-color, 0.1);
position: absolute;
}
&:hover {
background-color: rgba($text-primary-color, 0.06);
}
.calendar-entry {
top: 0.1rem;
bottom: 0;
width: auto !important;
left: 0.1rem !important;
right: 0.1rem;
}
}
.calendar-cell:first-child:not(:empty)::before {
width: 100%;
top: 0;
border-left: none;
border-top: solid 1px rgba($text-primary-color, 0.1);
}
}
.calendar-cell:first-child {
position: absolute;
width: 6rem;
left: 0;
text-align: center;
}
.calendar-link {
display: block;
width: 100%;
height: 100%;
}
}
.color-picker {
.color-picker-entry {
position: relative;
width: 4rem;
height: 4rem;
float: left;
input {
visibility: hidden;
}
label {
position: absolute;
top: 0.5rem;
left: 0.5rem;
width: 3rem;
height: 3rem;
border-radius: 1.5rem;
z-index: 1;
}
input:checked ~ label::before {
content: '';
position: absolute;
background-color: transparent;
top: 1rem;
left: 0.8rem;
width: 1.1rem;
height: 0.5rem;
transform: rotate(-45deg);
border-left: solid 0.3rem $background-primary-color;
border-bottom: solid 0.3rem $background-primary-color;
}
}
.color-picker-custom {
position: relative;
clear: both;
height: 4rem;
& > input {
visibility: hidden;
}
label {
position: absolute;
top: 0.5rem;
left: 0.5rem;
width: 3rem;
height: 3rem;
border-radius: 1.5rem;
z-index: 1;
border: solid 0.1rem $text-primary-color;
}
input:checked ~ label::before {
content: '';
position: absolute;
background-color: transparent;
top: 1rem;
left: 0.8rem;
width: 1.1rem;
height: 0.5rem;
transform: rotate(-45deg);
border-left: solid 0.3rem $text-primary-color;
border-bottom: solid 0.3rem $text-primary-color;
}
label input {
position: absolute;
left: 4rem;
top: 0.2rem;
height: 2rem;
width: 4rem;
}
}
}