diff options
Diffstat (limited to 'modules')
4 files changed, 104 insertions, 11 deletions
diff --git a/modules/orgflow-ui/src/wasmJsMain/kotlin/jp/orgflow/ui/runtime/WasmDistributionRuntime.kt b/modules/orgflow-ui/src/wasmJsMain/kotlin/jp/orgflow/ui/runtime/WasmDistributionRuntime.kt index 5452b94..ceec974 100644 --- a/modules/orgflow-ui/src/wasmJsMain/kotlin/jp/orgflow/ui/runtime/WasmDistributionRuntime.kt +++ b/modules/orgflow-ui/src/wasmJsMain/kotlin/jp/orgflow/ui/runtime/WasmDistributionRuntime.kt @@ -47,7 +47,7 @@ import kotlinx.serialization.json.Json class WasmDistributionRuntime( private val selfPeerId: String = "wasm-${Clock.System.now().toEpochMilliseconds() % 100000}", private var signalingEndpoint: String = OrgFlowAppStore.DEFAULT_SIGNALING_ENDPOINT, - private val roomId: String = "default", + private var roomId: String = "default", private var iceServers: List<String> = OrgFlowAppStore.DEFAULT_ICE_SERVERS, ) : DistributionRuntime, ConfigRuntime, SlideOpTransport { @@ -177,6 +177,15 @@ class WasmDistributionRuntime( refreshSnapshot() } + fun setRoom(newRoomId: String) { + val next = newRoomId.trim().ifEmpty { "default" } + if (next == roomId) return + roomId = next + lastEvent = "room switched to $next" + refreshSnapshot() + if (started) restart() + } + fun localVideoSink(): RtcVideoSink? = rtcPlatform?.localVideoSink() override fun sendChat(gid: String, body: String, targetPeerIds: List<String>, threadId: String) { diff --git a/modules/orgflow-ui/src/wasmJsMain/kotlin/jp/orgflow/web/WebApp.kt b/modules/orgflow-ui/src/wasmJsMain/kotlin/jp/orgflow/web/WebApp.kt index 67d3c5e..b571fe4 100644 --- a/modules/orgflow-ui/src/wasmJsMain/kotlin/jp/orgflow/web/WebApp.kt +++ b/modules/orgflow-ui/src/wasmJsMain/kotlin/jp/orgflow/web/WebApp.kt @@ -2,6 +2,7 @@ package jp.orgflow.web import androidx.compose.foundation.background +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -57,6 +58,7 @@ enum class WebRoute(val label: String) { NOTE("Note"), CALENDAR("Calendar"), LIVE("Live"), + CONFIG("Config"), } class WebNavState { @@ -72,6 +74,7 @@ fun WebApp(runtime: OrgFlowRuntime = remember { provideRuntime() }) { val persistence = remember { StorePersistenceController(store) } val scope = androidx.compose.runtime.rememberCoroutineScope() val wasm = runtime.distribution as? jp.orgflow.ui.runtime.WasmDistributionRuntime + val filter = remember { jp.orgflow.ui.model.SetFilterState() } LaunchedEffect(runtime, store) { val autoEndpoint = webAutoEndpoint(OrgFlowAppStore.DEFAULT_SIGNALING_ENDPOINT) @@ -113,19 +116,30 @@ fun WebApp(runtime: OrgFlowRuntime = remember { provideRuntime() }) { } } + LaunchedEffect(store.joinedRoomIds) { + wasm?.setRoom(store.joinedRoomIds.lastOrNull() ?: "default") + } + WebTheme { androidx.compose.material.Surface(modifier = Modifier.fillMaxSize(), color = MaterialTheme.colors.background) { - Row(modifier = Modifier.fillMaxSize()) { - WebNavRail(nav, modifier = Modifier.fillMaxHeight().width(120.dp)) - Column(modifier = Modifier.weight(1f).fillMaxHeight().padding(12.dp)) { - when (nav.current) { - WebRoute.HOME -> WebHomeScreen(store, runtime) - WebRoute.CREATE -> WebCreateScreen(store) - WebRoute.NOTE -> WebNoteScreen(store) - WebRoute.CALENDAR -> WebCalendarScreen(store) - WebRoute.LIVE -> WebLiveScreen(store, runtime) + androidx.compose.foundation.layout.Box(modifier = Modifier.fillMaxSize()) { + Row(modifier = Modifier.fillMaxSize()) { + WebNavRail(nav, modifier = Modifier.fillMaxHeight().width(120.dp)) + Column(modifier = Modifier.weight(1f).fillMaxHeight().padding(12.dp)) { + when (nav.current) { + WebRoute.HOME -> WebHomeScreen(store, runtime) + WebRoute.CREATE -> WebCreateScreen(store) + WebRoute.NOTE -> WebNoteScreen(store) + WebRoute.CALENDAR -> WebCalendarScreen(store) + WebRoute.LIVE -> WebLiveScreen(store, runtime) + WebRoute.CONFIG -> WebConfigScreen(runtime) + } } } + jp.orgflow.ui.pie.PieMenu( + filter = filter, + modifier = Modifier.align(Alignment.BottomEnd).padding(12.dp), + ) } } } @@ -168,6 +182,7 @@ private fun WebNavRail(nav: WebNavState, modifier: Modifier = Modifier) { color = if (selected) MaterialTheme.colors.primary.copy(alpha = 0.16f) else Color.Transparent, shape = RoundedCornerShape(8.dp), ) + .clickable { nav.current = route } .padding(horizontal = 12.dp, vertical = 10.dp), verticalAlignment = Alignment.CenterVertically, ) { diff --git a/modules/orgflow-ui/src/wasmJsMain/kotlin/jp/orgflow/web/WebConfig.kt b/modules/orgflow-ui/src/wasmJsMain/kotlin/jp/orgflow/web/WebConfig.kt new file mode 100644 index 0000000..95ecc8b --- /dev/null +++ b/modules/orgflow-ui/src/wasmJsMain/kotlin/jp/orgflow/web/WebConfig.kt @@ -0,0 +1,53 @@ +package jp.orgflow.web + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import jp.orgflow.ui.runtime.OrgFlowRuntime +import jp.orgflow.ui.runtime.WasmDistributionRuntime + +@Composable +fun WebConfigScreen(runtime: OrgFlowRuntime) { + val config by runtime.config.config.collectAsState() + val snapshot by runtime.distribution.snapshot.collectAsState() + val wasm = runtime.distribution as? WasmDistributionRuntime + var endpoint by remember(config.signalingEndpoint) { mutableStateOf(config.signalingEndpoint) } + var ice by remember(config.iceServers) { mutableStateOf(config.iceServers.joinToString(", ")) } + var message by remember { mutableStateOf<String?>(null) } + + WebScrollColumn { + WebSection("接続設定") { + WebTextField(endpoint, { endpoint = it }, "シグナリング URL (ws://… または wss://…)") + WebActionButton("シグナリングを適用") { + if (endpoint.trim().isNotBlank()) { + wasm?.updateEndpoint(endpoint.trim()) + message = "シグナリングを更新しました" + } + } + WebTextField(ice, { ice = it }, "ICE サーバ (カンマ区切り)") + WebActionButton("ICE サーバを適用") { + val servers = ice.split(",").map { it.trim() }.filter { it.isNotBlank() } + if (servers.isNotEmpty()) { + wasm?.updateIceServers(servers) + message = "ICE サーバを更新しました (再接続後に有効)" + } + } + message?.let { WebStatusLine(it) } + } + WebSection("現在の状態") { + WebStatusLine("ピアID: ${snapshot.selfPeerId}") + WebStatusLine("シグナリング: ${snapshot.signalingEndpoint}") + WebStatusLine("接続: ${if (snapshot.connected) "OK" else "未接続"} / エンジン: ${if (snapshot.engineReady) "OK" else "待機中"}") + WebStatusLine("ピア数: ${snapshot.peers.size}") + WebStatusLine("最新イベント: ${snapshot.lastEvent}") + } + WebSection("ヒント") { + WebStatusLine("カメラ/画面共有は https または localhost でのみ使用できます") + WebStatusLine("他のピアとつながらない場合は同じシグナリング URL・同じルームID か確認してください") + WebStatusLine("wss を使う場合は ops/HTTPS-SETUP.md (lighttpd設定) を参照してください") + } + } +} diff --git a/modules/orgflow-ui/src/wasmJsMain/kotlin/jp/orgflow/web/WebHome.kt b/modules/orgflow-ui/src/wasmJsMain/kotlin/jp/orgflow/web/WebHome.kt index d2de2d6..52b4721 100644 --- a/modules/orgflow-ui/src/wasmJsMain/kotlin/jp/orgflow/web/WebHome.kt +++ b/modules/orgflow-ui/src/wasmJsMain/kotlin/jp/orgflow/web/WebHome.kt @@ -71,9 +71,14 @@ fun WebHomeScreen(store: OrgFlowAppStore, runtime: OrgFlowRuntime) { @Composable private fun WebRoomsSection(store: OrgFlowAppStore) { var showRegister by remember { mutableStateOf(false) } + var quickId by remember { mutableStateOf("ws-classroom") } WebSection("ルーム") { if (store.joinableRooms.isEmpty()) { - WebStatusLine("ルームがありません (ルームを作成して共有しましょう)") + Row(horizontalArrangement = Arrangement.spacedBy(6.dp)) { + listOf("ws-classroom" to "Classroom", "ws-club" to "Club", "ws-committee" to "Committee").forEach { (id, name) -> + WebActionButton(name, primary = false) { store.registerRoom(id, name) } + } + } } else { store.joinableRooms.forEach { room -> val joined = room.id in store.joinedRoomIds @@ -87,6 +92,17 @@ private fun WebRoomsSection(store: OrgFlowAppStore) { } } } + Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(8.dp)) { + WebTextField(quickId, { quickId = it }, "ルームIDを入力して参加", modifier = Modifier.weight(1f)) + WebActionButton("参加", enabled = quickId.trim().isNotBlank()) { + val id = quickId.trim() + if (store.joinableRooms.none { it.id == id }) { + store.registerRoom(id, id) + } else { + store.joinRoom(id) + } + } + } WebActionButton(if (showRegister) "閉じる" else "ルームを作る", primary = false) { showRegister = !showRegister } if (showRegister) { RegisterRoomForm(store) |
