From 6be8114dff4257f15dd2b059554ef137dd8d9462 Mon Sep 17 00:00:00 2001 From: Stef Tervelde Date: Sun, 17 Nov 2024 13:53:21 +0100 Subject: [PATCH] Show local contributions --- .../app/contrib/ui/ContributionManager.kt | 80 ++++++++++++++++--- .../processing/app/contrib/ui/Preferences.kt | 71 ++++++++++++++++ 2 files changed, 138 insertions(+), 13 deletions(-) create mode 100644 app/src/processing/app/contrib/ui/Preferences.kt diff --git a/app/src/processing/app/contrib/ui/ContributionManager.kt b/app/src/processing/app/contrib/ui/ContributionManager.kt index 8f91401ee..6d63074ab 100644 --- a/app/src/processing/app/contrib/ui/ContributionManager.kt +++ b/app/src/processing/app/contrib/ui/ContributionManager.kt @@ -6,17 +6,13 @@ import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember +import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.input.pointer.PointerIcon import androidx.compose.ui.input.pointer.pointerHoverIcon import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.text.style.LineHeightStyle import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.compose.ui.window.Window @@ -24,12 +20,22 @@ import androidx.compose.ui.window.application import com.charleskorn.kaml.Yaml import com.charleskorn.kaml.YamlConfiguration import kotlinx.serialization.Serializable +import processing.app.Platform import java.net.URL +import java.util.* +import kotlin.io.path.* fun main() = application { - Window(onCloseRequest = ::exitApplication) { - ContributionsManager() + val active = remember { mutableStateOf(true) } + if(!active.value){ + Window(onCloseRequest = ::exitApplication) { + + } + return@application + } + Window(onCloseRequest = { active.value = false }) { + contributionsManager() } } @@ -77,10 +83,56 @@ data class Contributions( ) @Composable -fun ContributionsManager(){ - val contributions = remember { mutableStateOf(arrayOf()) } +fun contributionsManager(){ + var contributions by remember { mutableStateOf(arrayOf()) } + var localContributions by remember { mutableStateOf(arrayOf()) } val error = remember { mutableStateOf(null) } + val preferences = loadPreferences() + val sketchBookPath = preferences.getProperty("sketchbook.path.four", Platform.getDefaultSketchbookFolder().path) + + LaunchedEffect(preferences){ + val sketchBook = Path(sketchBookPath) + sketchBook.forEachDirectoryEntry{ contents -> + val typeName = contents.fileName.toString() + if(!contents.isDirectory()) return@forEachDirectoryEntry + contents.forEachDirectoryEntry { folder -> + if(!folder.isDirectory()) return@forEachDirectoryEntry + folder.forEachDirectoryEntry("*.properties"){ entry -> + val props = Properties() + props.load(entry.inputStream()) + + val type: Type = when(typeName){ + "libraries" -> Type.library + "modes" -> Type.mode + "tools" -> Type.tool + "examples" -> Type.examples + else -> return@forEachDirectoryEntry + } + + val contribution = Contribution( + id = 0, + status = Status.VALID, + source = entry.toString(), + type = type, + name = props.getProperty("name"), + authors = props.getProperty("authors"), + url = props.getProperty("url"), + sentence = props.getProperty("sentence"), + paragraph = props.getProperty("paragraph"), + version = props.getProperty("version"), + prettyVersion = props.getProperty("prettyVersion"), + minRevision = props.getProperty("minRevision")?.toIntOrNull(), + maxRevision = props.getProperty("maxRevision")?.toIntOrNull(), + download = props.getProperty("download"), + ) + localContributions += contribution + } + } + } + } + + LaunchedEffect(Unit){ try { val url = URL("https://github.com/mingness/processing-contributions-new/raw/refs/heads/main/contributions.yaml") @@ -96,7 +148,7 @@ fun ContributionsManager(){ ) val result = parser.decodeFromString(Contributions.serializer(), yaml) - contributions.value = result.contributions + contributions = result.contributions .filter { it.status == Status.VALID } .map { // TODO Parse better @@ -117,12 +169,12 @@ fun ContributionsManager(){ Text("Error loading contributions: ${error.value}") return } - if(contributions.value.isEmpty()){ + if(contributions.isEmpty()){ Text("Loading contributions...") return } - val contributionsByType = contributions.value.groupBy { it.type } + val contributionsByType = (contributions + localContributions).groupBy { it.type } val types = Type.entries val selectedType = remember { mutableStateOf(types.first()) } val contributionsForType = (contributionsByType[selectedType.value] ?: emptyList()) @@ -136,6 +188,7 @@ fun ContributionsManager(){ for(type in types){ Text(type.name, modifier = Modifier .background(if(selectedType.value == type) Color.Gray else Color.Transparent) + .pointerHoverIcon(PointerIcon.Hand) .clickable { selectedType.value = type selectedContribution.value = null @@ -156,7 +209,8 @@ fun ContributionsManager(){ Row(modifier = Modifier .pointerHoverIcon(PointerIcon.Hand) .clickable { selectedContribution.value = contribution } - .padding(8.dp) + .padding(8.dp), + horizontalArrangement = Arrangement.spacedBy(8.dp) ) { Row(modifier = Modifier.weight(1f)){ Text("status") diff --git a/app/src/processing/app/contrib/ui/Preferences.kt b/app/src/processing/app/contrib/ui/Preferences.kt new file mode 100644 index 000000000..b344e1cb7 --- /dev/null +++ b/app/src/processing/app/contrib/ui/Preferences.kt @@ -0,0 +1,71 @@ +package processing.app.contrib.ui + +import androidx.compose.runtime.* +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import processing.app.Base +import processing.app.Platform +import java.io.File +import java.nio.file.* +import java.util.Properties + + +const val PREFERENCES_FILE_NAME = "preferences.txt" +const val DEFAULTS_FILE_NAME = "defaults.txt" + + +@Composable +fun loadPreferences(): Properties{ + Platform.init() + + val settingsFolder = Platform.getSettingsFolder() + val preferencesFile = settingsFolder.resolve(PREFERENCES_FILE_NAME) + + if(!preferencesFile.exists()){ + preferencesFile.createNewFile() + } + val watched = watchFile(preferencesFile) + + val preferences by remember { + mutableStateOf(Properties()) + } + + LaunchedEffect(watched){ + val defaults = Base::class.java.getResourceAsStream("/lib/${DEFAULTS_FILE_NAME}") ?: return@LaunchedEffect + + preferences.load(defaults) + preferences.load(preferencesFile.inputStream()) + } + + return preferences +} + +@Composable +fun watchFile(file: File): Any? { + val scope = rememberCoroutineScope() + var event by remember(file) { mutableStateOf?> (null) } + + DisposableEffect(file){ + val fileSystem = FileSystems.getDefault() + val watcher = fileSystem.newWatchService() + var active = true + + val path = file.toPath() + val parent = path.parent + val key = parent.register(watcher, StandardWatchEventKinds.ENTRY_MODIFY) + scope.launch(Dispatchers.IO) { + while (active) { + for (modified in key.pollEvents()) { + if (modified.context() != path.fileName) continue + event = modified + } + } + } + onDispose { + active = false + key.cancel() + watcher.close() + } + } + return event +} \ No newline at end of file