init projects

This commit is contained in:
2026-06-17 18:58:41 +03:00
parent 8543c4e241
commit 21a768edd5
505 changed files with 19998 additions and 93 deletions
+4
View File
@@ -0,0 +1,4 @@
build
videobox.db
cache
bin
+35
View File
@@ -0,0 +1,35 @@
@file:OptIn(ExperimentalWasmDsl::class)
import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl
plugins {
alias(libs.plugins.convention.web.app)
alias(libs.plugins.convention.kmp)
alias(libs.plugins.convention.compose)
alias(libs.plugins.convention.koin)
}
kotlin {
sourceSets {
val wasmJsMain by getting {
dependencies {
implementation(project(":apps:vbox:client:shared"))
implementation(project(":apps:vbox:common"))
implementation(project(":libs:base"))
implementation(project(":libs:ui"))
implementation(project(":libs:http-client"))
implementation(project(":libs:video-player"))
}
}
}
wasmJs {
browser {
commonWebpackConfig {
outputFileName = "composeApp.js"
}
}
binaries.executable()
}
}
@@ -0,0 +1,114 @@
package ru.shadowsparky.vbox
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.asPaddingValues
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.safeDrawing
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.window.ComposeViewport
import com.arkivanov.decompose.DefaultComponentContext
import com.arkivanov.decompose.ExperimentalDecomposeApi
import com.arkivanov.decompose.router.webhistory.withWebHistory
import com.arkivanov.essenty.lifecycle.LifecycleRegistry
import com.arkivanov.essenty.lifecycle.doOnResume
import com.arkivanov.essenty.statekeeper.StateKeeper
import kotlinx.browser.document
import org.koin.core.annotation.KoinApplication
import org.koin.core.annotation.Module
import org.koin.core.annotation.Single
import org.koin.plugin.module.dsl.startKoin
import org.w3c.dom.HTMLElement
import ru.shadowsparky.domain.AppConfig
import ru.shadowsparky.koin
import ru.shadowsparky.ui.hideElementById
import ru.shadowsparky.ui.hookPageVisibility
import ru.shadowsparky.vbox.shared.presentation.Nav
import ru.shadowsparky.vbox.shared.presentation.RootComponent
import ru.shadowsparky.vbox.shared.presentation.RootFactory
import ru.shadowsparky.vbox.shared.presentation.UrlRoutes
import ru.shadowsparky.vbox.shared.presentation.nav.Route
@KoinApplication
object VBox
@Module
class AppModule {
@Single
fun provideAppConfig(): AppConfig {
return object : AppConfig {
override fun isDebug(): Boolean = true
}
}
}
fun main() {
try {
mainInternal()
} catch (e: Throwable) {
println(e.stackTraceToString())
throw e
}
}
@OptIn(ExperimentalDecomposeApi::class, ExperimentalComposeUiApi::class)
fun mainInternal() {
val lifecycle = LifecycleRegistry()
startKoin<VBox>()
val composeRoot = document.getElementById("compose-root") as HTMLElement
var rootRef: RootComponent? = null
val root: RootComponent = withWebHistory { stateKeeper: StateKeeper, deepLinkUrl: String? ->
if (rootRef == null) {
rootRef = createRoot(lifecycle, stateKeeper, deepLinkUrl)
}
rootRef
}
hookPageVisibility(lifecycle)
lifecycle.doOnResume { hideElementById("loader") }
ComposeViewport(viewportContainer = composeRoot, content = {
val r = remember { root }
LaunchedEffect(null) {
r.onFinish.collect { r.navReplace(Route.Loading()) }
}
val isLoggedIn by r.isLoggedIn.collectAsState(null)
LaunchedEffect(isLoggedIn) {
if (isLoggedIn == false) {
r.navReplace(Route.Loading())
}
}
Box(Modifier.fillMaxSize().padding(WindowInsets.safeDrawing.asPaddingValues())) {
Nav(r)
}
})
}
private fun createRoot(
lifecycle: LifecycleRegistry,
stateKeeper: StateKeeper,
deepLinkUrl: String?,
): RootComponent {
val ctx = DefaultComponentContext(
lifecycle = lifecycle, stateKeeper = stateKeeper
)
val deep = deepLinkUrl?.let { runCatching { UrlRoutes.parseUrl(it) }.getOrNull() }
val initRoute: Route = deep ?: Route.default
return koin.get<RootFactory>().create(context = ctx, initStack = initRoute)
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 663 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

@@ -0,0 +1,83 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
<title>VBox</title>
<base href="/"/>
<link href="/manifest.json" rel="manifest">
<meta content="width=device-width, initial-scale=1, viewport-fit=cover" name="viewport"/>
<style>
#loader {
position: fixed;
inset: 0;
display: flex;
justify-content: center;
align-items: center;
background: #111;
z-index: 9999;
}
.spinner {
width: 50px;
height: 50px;
border: 6px solid #ccc;
border-top-color: #09f;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
html, body {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
background: #000;
overflow: hidden;
}
#vboxCanvas {
display: block;
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
height: 100dvh;
margin: 0;
padding: 0;
background: #000;
touch-action: manipulation;
}
</style>
<!-- грузим скрипты после DOM -->
<script defer src="/composeApp.js"></script>
<meta content="yes" name="apple-mobile-web-app-capable">
<meta content="black" name="apple-mobile-web-app-status-bar-style">
<meta content="VBox" name="apple-mobile-web-app-title">
<link href="icon.png" rel="apple-touch-icon">
<link href="icon.png" rel="icon" sizes="192x192" type="image/png">
<link href="icon-32.png" rel="icon" sizes="32x32" type="image/png">
<link href="icon-16.png" rel="icon" sizes="16x16" type="image/png">
</head>
<body>
<div id="app-container" style="position:fixed; inset:0; overflow:hidden;">
<div id="loader">
<div class="spinner"></div>
</div>
<div id="video-layer"
style="position:absolute; inset:0; background:#000; visibility: hidden;"></div>
<div id="compose-root" style="position:absolute; inset:0;"></div>
</div>
</body>
</html>
@@ -0,0 +1,13 @@
{
"name": "Videobox",
"short_name": "VBox",
"start_url": "/",
"display": "standalone",
"icons": [
{
"src": "icon.png",
"sizes": "192x192",
"type": "image/png"
}
]
}
@@ -0,0 +1,58 @@
import initSqlJs from "sql.js";
let db = null;
async function createDatabase() {
let SQL = await initSqlJs({locateFile: file => 'sql-wasm.wasm'});
db = new SQL.Database();
}
function onModuleReady() {
const data = this.data;
switch (data && data.action) {
case "exec":
if (!data["sql"]) {
throw new Error("exec: Missing query string");
}
return postMessage({
id: data.id,
results: db.exec(data.sql, data.params)[0] ?? {values: []}
});
case "begin_transaction":
return postMessage({
id: data.id,
results: db.exec("BEGIN TRANSACTION;")
})
case "end_transaction":
return postMessage({
id: data.id,
results: db.exec("END TRANSACTION;")
})
case "rollback_transaction":
return postMessage({
id: data.id,
results: db.exec("ROLLBACK TRANSACTION;")
})
default:
throw new Error(`Unsupported action: ${data && data.action}`);
}
}
function onError(err) {
return postMessage({
id: this.data.id,
error: err
});
}
if (typeof importScripts === "function") {
db = null;
const sqlModuleReady = createDatabase()
self.onmessage = (event) => {
return sqlModuleReady
.then(onModuleReady.bind(event))
.catch(onError.bind(event));
}
}
@@ -0,0 +1,15 @@
const TerserPlugin = require("terser-webpack-plugin");
config.optimization = config.optimization || {};
config.optimization.minimize = true;
config.optimization.minimizer = [
new TerserPlugin({
terserOptions: {
mangle: true, // Note: By default, mangle is set to true.
compress: false, // Disable the transformations that reduce the code size.
output: {
beautify: false,
},
},
}),
];
@@ -0,0 +1,4 @@
config.devServer = {
...config.devServer,
historyApiFallback: true
};
@@ -0,0 +1,17 @@
// {project}/webpack.config.d/sqljs.js
config.resolve = {
fallback: {
fs: false,
path: false,
crypto: false,
}
};
const CopyWebpackPlugin = require('copy-webpack-plugin');
config.plugins.push(
new CopyWebpackPlugin({
patterns: [
'../../node_modules/sql.js/dist/sql-wasm.wasm'
]
})
);