mirror of
https://github.com/InsanusMokrassar/MicroUtils.git
synced 2025-09-17 14:29:24 +00:00
Compare commits
19 Commits
Author | SHA1 | Date | |
---|---|---|---|
5b325a8ff9 | |||
4582e0c817 | |||
e2d1c5d6a1 | |||
4019ad7d31 | |||
e7df21e91a | |||
b2e30c9f6d | |||
0b27b5cc06 | |||
347e9c32fe | |||
cc18f58e4c | |||
dbd2e963a1 | |||
6928ca5329 | |||
9ece160aa8 | |||
6115c1bcac | |||
e026e94cbf | |||
56cdd8d6af | |||
899e6760e1 | |||
864d0ffcc6 | |||
c6f417f8c8 | |||
9091fa5bd8 |
21
CHANGELOG.md
21
CHANGELOG.md
@@ -1,5 +1,25 @@
|
||||
# Changelog
|
||||
|
||||
## 0.2.3
|
||||
|
||||
* `Versions`
|
||||
* `Coroutines`: `1.3.9` -> `1.4.0`
|
||||
* `Exposed`: `0.27.1` -> `0.28.1`
|
||||
* `Common`
|
||||
* `K/JS`
|
||||
* Add several extensions for `Element` objects to detect that object is on screen viewport
|
||||
* Add several extensions for `Element` objects to detect object visibility
|
||||
* `Coroutines`
|
||||
* `BroadcastStateFlow` now use different strategy for getting of state and implements `replayCache`
|
||||
|
||||
## 0.2.2
|
||||
|
||||
* `Repos`
|
||||
* `Common`
|
||||
* Several new methods `ReadOneToManyKeyValueRepo#getAll`
|
||||
* Several new method `WriteOneToManyKeyValueRepo#add` and several extensions
|
||||
* Several new method `WriteOneToManyKeyValueRepo#remove` and several extensions
|
||||
|
||||
## 0.2.1
|
||||
|
||||
* `Pagination`
|
||||
@@ -7,6 +27,7 @@
|
||||
* Extension `Pagination#reverse` has been added
|
||||
* Factory `PaginationByIndexes`
|
||||
* Shortcut `calculatePagesNumber` with reversed parameters
|
||||
* Value `emptyPagination` for empty `SimplePagination` cases
|
||||
|
||||
## 0.2.0
|
||||
|
||||
|
@@ -0,0 +1,43 @@
|
||||
package dev.inmo.micro_utils.common
|
||||
|
||||
import kotlinx.browser.window
|
||||
import org.w3c.dom.DOMRect
|
||||
import org.w3c.dom.Element
|
||||
|
||||
val DOMRect.isOnScreenByLeftEdge: Boolean
|
||||
get() = left >= 0 && left <= window.innerWidth
|
||||
inline val Element.isOnScreenByLeftEdge
|
||||
get() = getBoundingClientRect().isOnScreenByLeftEdge
|
||||
|
||||
val DOMRect.isOnScreenByRightEdge: Boolean
|
||||
get() = right >= 0 && right <= window.innerWidth
|
||||
inline val Element.isOnScreenByRightEdge
|
||||
get() = getBoundingClientRect().isOnScreenByRightEdge
|
||||
|
||||
internal val DOMRect.isOnScreenHorizontally: Boolean
|
||||
get() = isOnScreenByLeftEdge || isOnScreenByRightEdge
|
||||
|
||||
|
||||
val DOMRect.isOnScreenByTopEdge: Boolean
|
||||
get() = top >= 0 && top <= window.innerHeight
|
||||
inline val Element.isOnScreenByTopEdge
|
||||
get() = getBoundingClientRect().isOnScreenByTopEdge
|
||||
|
||||
val DOMRect.isOnScreenByBottomEdge: Boolean
|
||||
get() = bottom >= 0 && bottom <= window.innerHeight
|
||||
inline val Element.isOnScreenByBottomEdge
|
||||
get() = getBoundingClientRect().isOnScreenByBottomEdge
|
||||
|
||||
internal val DOMRect.isOnScreenVertically: Boolean
|
||||
get() = isOnScreenByLeftEdge || isOnScreenByRightEdge
|
||||
|
||||
|
||||
val DOMRect.isOnScreenFully: Boolean
|
||||
get() = isOnScreenByLeftEdge && isOnScreenByTopEdge && isOnScreenByRightEdge && isOnScreenByBottomEdge
|
||||
val Element.isOnScreenFully: Boolean
|
||||
get() = getBoundingClientRect().isOnScreenFully
|
||||
|
||||
val DOMRect.isOnScreen: Boolean
|
||||
get() = isOnScreenFully || (isOnScreenHorizontally && isOnScreenVertically)
|
||||
inline val Element.isOnScreen: Boolean
|
||||
get() = getBoundingClientRect().isOnScreen
|
@@ -0,0 +1,48 @@
|
||||
package dev.inmo.micro_utils.common
|
||||
|
||||
import kotlinx.browser.window
|
||||
import org.w3c.dom.Element
|
||||
import org.w3c.dom.css.CSSStyleDeclaration
|
||||
|
||||
sealed class Visibility
|
||||
object Visible : Visibility()
|
||||
object Invisible : Visibility()
|
||||
object Gone : Visibility()
|
||||
|
||||
var CSSStyleDeclaration.visibilityState: Visibility
|
||||
get() = when {
|
||||
display == "none" -> Gone
|
||||
visibility == "hidden" -> Invisible
|
||||
else -> Visible
|
||||
}
|
||||
set(value) {
|
||||
when (value) {
|
||||
Visible -> {
|
||||
if (display == "none") {
|
||||
display = "initial"
|
||||
}
|
||||
visibility = "visible"
|
||||
}
|
||||
Invisible -> {
|
||||
if (display == "none") {
|
||||
display = "initial"
|
||||
}
|
||||
visibility = "hidden"
|
||||
}
|
||||
Gone -> {
|
||||
display = "none"
|
||||
}
|
||||
}
|
||||
}
|
||||
inline var Element.visibilityState: Visibility
|
||||
get() = window.getComputedStyle(this).visibilityState
|
||||
set(value) {
|
||||
window.getComputedStyle(this).visibilityState = value
|
||||
}
|
||||
|
||||
inline val Element.isVisible: Boolean
|
||||
get() = visibilityState == Visible
|
||||
inline val Element.isInvisible: Boolean
|
||||
get() = visibilityState == Invisible
|
||||
inline val Element.isGone: Boolean
|
||||
get() = visibilityState == Gone
|
@@ -5,29 +5,59 @@ import kotlinx.coroutines.channels.BroadcastChannel
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.flow.*
|
||||
|
||||
const val defaultBroadcastStateFlowReplayCacheSize = 1
|
||||
|
||||
class BroadcastStateFlow<T> internal constructor(
|
||||
parentFlow: Flow<T>,
|
||||
private val stateGetter: () -> T
|
||||
initial: T,
|
||||
replayCacheSize: Int = defaultBroadcastStateFlowReplayCacheSize,
|
||||
replayScope: CoroutineScope
|
||||
) : StateFlow<T>, Flow<T> by parentFlow {
|
||||
private val deque = ArrayDeque<T>(1).also {
|
||||
it.add(initial)
|
||||
}
|
||||
override val replayCache: List<T>
|
||||
get() = deque.toList()
|
||||
override val value: T
|
||||
get() = stateGetter()
|
||||
}
|
||||
get() = deque.last()
|
||||
|
||||
fun <T> BroadcastChannel<T>.asStateFlow(value: T, scope: CoroutineScope): StateFlow<T> = asFlow().let {
|
||||
var state: T = value
|
||||
it.onEach { state = it }.launchIn(scope)
|
||||
BroadcastStateFlow(it) {
|
||||
state
|
||||
init {
|
||||
if (replayCacheSize < 1) {
|
||||
error("Replay cache size can't be less than 1, but was $replayCacheSize")
|
||||
}
|
||||
parentFlow.onEach {
|
||||
deque.addLast(it)
|
||||
if (deque.size > replayCacheSize) {
|
||||
deque.removeFirst()
|
||||
}
|
||||
}.launchIn(replayScope)
|
||||
}
|
||||
}
|
||||
|
||||
fun <T> BroadcastChannel<T?>.asStateFlow(scope: CoroutineScope): StateFlow<T?> = asStateFlow(null, scope)
|
||||
fun <T> BroadcastChannel<T>.asStateFlow(
|
||||
value: T,
|
||||
scope: CoroutineScope,
|
||||
replayCacheSize: Int = defaultBroadcastStateFlowReplayCacheSize
|
||||
): StateFlow<T> = BroadcastStateFlow(asFlow(), value, replayCacheSize, scope)
|
||||
|
||||
fun <T> broadcastStateFlow(initial: T, scope: CoroutineScope, channelSize: Int = Channel.BUFFERED) = BroadcastChannel<T>(
|
||||
fun <T> BroadcastChannel<T?>.asStateFlow(
|
||||
scope: CoroutineScope,
|
||||
replayCacheSize: Int = defaultBroadcastStateFlowReplayCacheSize
|
||||
): StateFlow<T?> = asStateFlow(null, scope, replayCacheSize)
|
||||
|
||||
fun <T> broadcastStateFlow(
|
||||
initial: T, scope: CoroutineScope,
|
||||
channelSize: Int = Channel.BUFFERED,
|
||||
replayCacheSize: Int = defaultBroadcastStateFlowReplayCacheSize
|
||||
) = BroadcastChannel<T>(
|
||||
channelSize
|
||||
).let {
|
||||
it to it.asStateFlow(initial, scope)
|
||||
it to it.asStateFlow(initial, scope, replayCacheSize)
|
||||
}
|
||||
|
||||
fun <T> broadcastStateFlow(scope: CoroutineScope, channelSize: Int = Channel.BUFFERED) = broadcastStateFlow<T?>(null, scope, channelSize)
|
||||
fun <T> broadcastStateFlow(
|
||||
scope: CoroutineScope,
|
||||
channelSize: Int = Channel.BUFFERED,
|
||||
replayCacheSize: Int = defaultBroadcastStateFlowReplayCacheSize
|
||||
) = broadcastStateFlow<T?>(null, scope, channelSize, replayCacheSize)
|
||||
|
||||
|
88
dokka/build.gradle
Normal file
88
dokka/build.gradle
Normal file
@@ -0,0 +1,88 @@
|
||||
buildscript {
|
||||
repositories {
|
||||
mavenLocal()
|
||||
jcenter()
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||
classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version"
|
||||
classpath "org.jetbrains.dokka:dokka-gradle-plugin:$dokka_version"
|
||||
}
|
||||
}
|
||||
|
||||
plugins {
|
||||
id "org.jetbrains.kotlin.multiplatform"
|
||||
id "org.jetbrains.kotlin.plugin.serialization"
|
||||
id "org.jetbrains.dokka" version "$dokka_version"
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenLocal()
|
||||
jcenter()
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
kotlin {
|
||||
jvm()
|
||||
js(BOTH) {
|
||||
browser()
|
||||
nodejs()
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
commonMain {
|
||||
dependencies {
|
||||
implementation kotlin('stdlib')
|
||||
|
||||
project.parent.subprojects.forEach {
|
||||
if (
|
||||
it != project
|
||||
&& it.hasProperty("kotlin")
|
||||
&& it.kotlin.sourceSets.any { it.name.contains("commonMain") }
|
||||
) {
|
||||
api it
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private List<SourceDirectorySet> findSourcesWithName(String... approximateNames) {
|
||||
return parent.subprojects
|
||||
.findAll { it != project && it.hasProperty("kotlin") }
|
||||
.collectMany { it.kotlin.sourceSets }
|
||||
.findAll { sourceSet ->
|
||||
approximateNames.any { nameToFilter ->
|
||||
sourceSet.name.contains(nameToFilter)
|
||||
}
|
||||
}.collect { it.kotlin }
|
||||
}
|
||||
|
||||
tasks.dokkaHtml {
|
||||
dokkaSourceSets {
|
||||
configureEach {
|
||||
skipDeprecated.set(true)
|
||||
|
||||
sourceLink {
|
||||
localDirectory.set(file("./"))
|
||||
remoteUrl.set(new URL("https://github.com/InsanusMokrassar/MicroUtils/blob/master/"))
|
||||
remoteLineSuffix.set("#L")
|
||||
}
|
||||
}
|
||||
|
||||
named("commonMain") {
|
||||
sourceRoots.setFrom(findSourcesWithName("commonMain"))
|
||||
}
|
||||
|
||||
named("jsMain") {
|
||||
sourceRoots.setFrom(findSourcesWithName("jsMain", "commonMain"))
|
||||
}
|
||||
|
||||
named("jvmMain") {
|
||||
sourceRoots.setFrom(findSourcesWithName("jvmMain", "commonMain"))
|
||||
}
|
||||
}
|
||||
}
|
3
dokka/gradle.properties
Normal file
3
dokka/gradle.properties
Normal file
@@ -0,0 +1,3 @@
|
||||
dokka_version=1.4.0
|
||||
|
||||
org.gradle.jvmargs=-Xmx1024m
|
@@ -5,9 +5,9 @@ kotlin.incremental=true
|
||||
kotlin.incremental.js=true
|
||||
|
||||
kotlin_version=1.4.10
|
||||
kotlin_coroutines_version=1.3.9
|
||||
kotlin_coroutines_version=1.4.0
|
||||
kotlin_serialisation_core_version=1.0.0
|
||||
kotlin_exposed_version=0.27.1
|
||||
kotlin_exposed_version=0.28.1
|
||||
|
||||
ktor_version=1.4.1
|
||||
|
||||
@@ -19,4 +19,4 @@ github_release_plugin_version=2.2.12
|
||||
uuidVersion=0.2.2
|
||||
|
||||
group=dev.inmo
|
||||
version=0.2.1
|
||||
version=0.2.3
|
||||
|
@@ -14,6 +14,8 @@ inline fun FirstPagePagination(size: Int = defaultMediumPageSize) =
|
||||
size = size
|
||||
)
|
||||
|
||||
val emptyPagination = Pagination(0, 0)
|
||||
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
inline fun Pagination.nextPage() =
|
||||
SimplePagination(
|
||||
|
@@ -10,22 +10,16 @@ import dev.inmo.micro_utils.pagination.*
|
||||
*
|
||||
* @return Reversed version of this [Pagination]
|
||||
*/
|
||||
fun Pagination.reverse(objectsCount: Long): SimplePagination {
|
||||
val firstIndex = (objectsCount - (this.lastIndex + 1)).let {
|
||||
when {
|
||||
it < 0 -> it
|
||||
it > objectsCount -> objectsCount
|
||||
else -> it
|
||||
}
|
||||
}.toInt()
|
||||
val lastIndex = (objectsCount - (this.firstIndex + 1)).let {
|
||||
when {
|
||||
it < 0 -> it
|
||||
it > objectsCount -> objectsCount
|
||||
else -> it
|
||||
}
|
||||
}.toInt()
|
||||
return PaginationByIndexes(firstIndex, lastIndex)
|
||||
fun Pagination.reverse(datasetSize: Long): SimplePagination {
|
||||
val pagesNumber = calculatePagesNumber(size, datasetSize)
|
||||
val newPage = pagesNumber - page - 1
|
||||
return when {
|
||||
page < 0 || page >= pagesNumber -> emptyPagination
|
||||
else -> Pagination(
|
||||
newPage,
|
||||
size
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -7,18 +7,18 @@ import kotlin.test.assertEquals
|
||||
class PaginationReversingTests {
|
||||
@Test
|
||||
fun testThatCommonCaseWorksOk() {
|
||||
val pageSize = 2
|
||||
val collectionSize = 8
|
||||
val pages = calculatePage(collectionSize, pageSize)
|
||||
val pageSize = 3
|
||||
val collectionSize = 9
|
||||
|
||||
doWithPagination(FirstPagePagination(pageSize)) {
|
||||
val reversed = it.reverse(collectionSize.toLong())
|
||||
assertEquals(Pagination(calculatePage(collectionSize - it.firstIndex - it.size, it.size), it.size), reversed)
|
||||
if (it.page < pages) {
|
||||
it.nextPage()
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
assertEquals(Pagination(-1, pageSize).reverse(collectionSize), Pagination(0, 0))
|
||||
|
||||
val middleFirstIndex = collectionSize / 2 - pageSize / 2
|
||||
val middleLastIndex = middleFirstIndex + pageSize - 1
|
||||
assertEquals(
|
||||
PaginationByIndexes(middleFirstIndex, middleLastIndex).reverse(collectionSize),
|
||||
PaginationByIndexes(middleFirstIndex, middleLastIndex)
|
||||
)
|
||||
|
||||
assertEquals(Pagination(calculatePagesNumber(collectionSize, pageSize), pageSize).reverse(collectionSize), Pagination(0, 0))
|
||||
}
|
||||
}
|
||||
|
@@ -1,7 +1,6 @@
|
||||
package dev.inmo.micro_utils.repos
|
||||
|
||||
import dev.inmo.micro_utils.pagination.Pagination
|
||||
import dev.inmo.micro_utils.pagination.PaginationResult
|
||||
import dev.inmo.micro_utils.pagination.*
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface ReadOneToManyKeyValueRepo<Key, Value> : Repo {
|
||||
@@ -11,6 +10,30 @@ interface ReadOneToManyKeyValueRepo<Key, Value> : Repo {
|
||||
suspend fun contains(k: Key, v: Value): Boolean
|
||||
suspend fun count(k: Key): Long
|
||||
suspend fun count(): Long
|
||||
|
||||
suspend fun getAll(k: Key, reversed: Boolean = false): List<Value> = mutableListOf<Value>().also { list ->
|
||||
doWithPagination {
|
||||
get(k, it).also {
|
||||
list.addAll(it.results)
|
||||
}.nextPageIfNotEmpty()
|
||||
}
|
||||
if (reversed) {
|
||||
list.reverse()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* WARNING!!! THIS METHOD PROBABLY IS NOT EFFICIENT, USE WITH CAUTION
|
||||
*/
|
||||
suspend fun getAll(reverseLists: Boolean = false): Map<Key, List<Value>> = mutableMapOf<Key, List<Value>>().also { map ->
|
||||
doWithPagination {
|
||||
keys(it).also { paginationResult ->
|
||||
paginationResult.results.forEach { k ->
|
||||
map[k] = getAll(k, reverseLists)
|
||||
}
|
||||
}.nextPageIfNotEmpty()
|
||||
}
|
||||
}
|
||||
}
|
||||
@Deprecated("Renamed", ReplaceWith("ReadOneToManyKeyValueRepo", "dev.inmo.micro_utils.repos.ReadOneToManyKeyValueRepo"))
|
||||
typealias OneToManyReadKeyValueRepo<Key, Value> = ReadOneToManyKeyValueRepo<Key, Value>
|
||||
@@ -20,11 +43,51 @@ interface WriteOneToManyKeyValueRepo<Key, Value> : Repo {
|
||||
val onValueRemoved: Flow<Pair<Key, Value>>
|
||||
val onDataCleared: Flow<Key>
|
||||
|
||||
suspend fun add(toAdd: Map<Key, List<Value>>) = toAdd.forEach { (k, values) ->
|
||||
values.forEach { v ->
|
||||
add(k, v)
|
||||
}
|
||||
}
|
||||
@Deprecated("Will be extracted as extension for other add method")
|
||||
suspend fun add(k: Key, v: Value)
|
||||
|
||||
suspend fun remove(toRemove: Map<Key, List<Value>>) = toRemove.forEach { (k, values) ->
|
||||
values.forEach { v ->
|
||||
remove(k, v)
|
||||
}
|
||||
}
|
||||
@Deprecated("Will be extracted as extension for other remove method")
|
||||
suspend fun remove(k: Key, v: Value)
|
||||
|
||||
suspend fun clear(k: Key)
|
||||
}
|
||||
@Deprecated("Renamed", ReplaceWith("WriteOneToManyKeyValueRepo", "dev.inmo.micro_utils.repos.WriteOneToManyKeyValueRepo"))
|
||||
typealias OneToManyWriteKeyValueRepo<Key, Value> = WriteOneToManyKeyValueRepo<Key, Value>
|
||||
|
||||
interface OneToManyKeyValueRepo<Key, Value> : ReadOneToManyKeyValueRepo<Key, Value>, WriteOneToManyKeyValueRepo<Key, Value>
|
||||
interface OneToManyKeyValueRepo<Key, Value> : ReadOneToManyKeyValueRepo<Key, Value>, WriteOneToManyKeyValueRepo<Key, Value>
|
||||
|
||||
suspend inline fun <Key, Value, REPO : WriteOneToManyKeyValueRepo<Key, Value>> REPO.add(
|
||||
k: Key,
|
||||
vararg v: Value
|
||||
) = add(mapOf(k to v.toList()))
|
||||
|
||||
suspend inline fun <Key, Value, REPO : WriteOneToManyKeyValueRepo<Key, Value>> REPO.add(
|
||||
keysAndValues: List<Pair<Key, List<Value>>>
|
||||
) = add(keysAndValues.toMap())
|
||||
|
||||
suspend inline fun <Key, Value, REPO : WriteOneToManyKeyValueRepo<Key, Value>> REPO.add(
|
||||
vararg keysAndValues: Pair<Key, List<Value>>
|
||||
) = add(keysAndValues.toMap())
|
||||
|
||||
suspend inline fun <Key, Value, REPO : WriteOneToManyKeyValueRepo<Key, Value>> REPO.remove(
|
||||
k: Key,
|
||||
vararg v: Value
|
||||
) = remove(mapOf(k to v.toList()))
|
||||
|
||||
suspend inline fun <Key, Value, REPO : WriteOneToManyKeyValueRepo<Key, Value>> REPO.remove(
|
||||
keysAndValues: List<Pair<Key, List<Value>>>
|
||||
) = remove(keysAndValues.toMap())
|
||||
|
||||
suspend inline fun <Key, Value, REPO : WriteOneToManyKeyValueRepo<Key, Value>> REPO.remove(
|
||||
vararg keysAndValues: Pair<Key, List<Value>>
|
||||
) = remove(keysAndValues.toMap())
|
||||
|
@@ -1,7 +1,7 @@
|
||||
package dev.inmo.micro_utils.repos.pagination
|
||||
|
||||
import dev.inmo.micro_utils.pagination.*
|
||||
import dev.inmo.micro_utils.repos.*
|
||||
import dev.inmo.micro_utils.repos.ReadStandardKeyValueRepo
|
||||
|
||||
suspend inline fun <Key, Value, REPO : ReadStandardKeyValueRepo<Key, Value>> REPO.doForAll(
|
||||
@Suppress("REDUNDANT_INLINE_SUSPEND_FUNCTION_TYPE")
|
||||
|
@@ -1,7 +1,7 @@
|
||||
package dev.inmo.micro_utils.repos.pagination
|
||||
|
||||
import dev.inmo.micro_utils.pagination.*
|
||||
import dev.inmo.micro_utils.repos.*
|
||||
import dev.inmo.micro_utils.repos.ReadOneToManyKeyValueRepo
|
||||
|
||||
suspend inline fun <Key, Value, REPO : ReadOneToManyKeyValueRepo<Key, Value>> REPO.doForAll(
|
||||
@Suppress("REDUNDANT_INLINE_SUSPEND_FUNCTION_TYPE")
|
||||
|
@@ -16,7 +16,9 @@ String[] includes = [
|
||||
":ktor:server",
|
||||
":ktor:common",
|
||||
":ktor:client",
|
||||
":coroutines"
|
||||
":coroutines",
|
||||
|
||||
":dokka"
|
||||
]
|
||||
|
||||
|
||||
|
Reference in New Issue
Block a user