diff --git a/.github/workflows/docs-pages.yml b/.github/workflows/docs-pages.yml new file mode 100644 index 0000000..4742392 --- /dev/null +++ b/.github/workflows/docs-pages.yml @@ -0,0 +1,73 @@ +name: Documentation Pages + +on: + push: + branches: [main] + paths: + - "docs/**" + - "mkdocs.yml" + - "requirements-docs.txt" + - "scripts/build-docs.ps1" + - ".github/workflows/docs-pages.yml" + pull_request: + paths: + - "docs/**" + - "mkdocs.yml" + - "requirements-docs.txt" + - "scripts/build-docs.ps1" + - ".github/workflows/docs-pages.yml" + +# Publication runs to completion instead of being cancelled by a later merge, +# so a half-written deploy never replaces a good site. Pull-request builds get +# their own group, so a merge never queues behind review feedback. +concurrency: + group: docs-pages-${{ github.ref }} + cancel-in-progress: false + +permissions: + contents: read + +jobs: + build: + name: Build the documentation site + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - name: Checkout + uses: actions/checkout@v7 + with: + persist-credentials: false + + - name: Set up Python + uses: actions/setup-python@v7 + with: + python-version: '3.12' + + - name: Build the site (strict) + shell: pwsh + run: pwsh -NoProfile -File scripts/build-docs.ps1 + + - name: Upload the built site + uses: actions/upload-pages-artifact@v5 + with: + path: site + + # The repository's Pages source is already GitHub Actions, so the first push + # to main that touches documentation publishes the site. No Unity, no + # credentials, and no generated content beyond the strict build. + deploy: + name: Publish to GitHub Pages + if: github.event_name == 'push' && github.ref == 'refs/heads/main' + needs: build + runs-on: ubuntu-latest + timeout-minutes: 10 + permissions: + pages: write + id-token: write + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + steps: + - name: Deploy + id: deployment + uses: actions/deploy-pages@v5 diff --git a/.gitignore b/.gitignore index efcf5b9..434e0a3 100644 --- a/.gitignore +++ b/.gitignore @@ -94,6 +94,11 @@ agents.config.json agents.config.json.meta opencode.json.meta +# Documentation site build output and its virtual environment +/site/ +/site.meta +.docs-venv/ + # Unity MCP bridge artifacts (editor state, registry, captures, generated images) .artifacts/ GOAL.md* diff --git a/docs/getting-started.md b/docs/getting-started.md new file mode 100644 index 0000000..1212beb --- /dev/null +++ b/docs/getting-started.md @@ -0,0 +1,112 @@ +# Getting started + +This page takes you from an empty project to an edited asset. It covers the +first session only. The +[README](https://github.com/wallstop/DataVisualizer#readme) documents the full +feature set, including themes, processors, and extension points. + +## Requirements + +- Unity 2021.3 or newer. `package.json` declares that floor, and the window is + developed and tested on Unity 6. The 2021.3 editor is not part of the local + validation yet, which + [#86](https://github.com/wallstop/DataVisualizer/issues/86) tracks. +- No additional package dependencies. + +## Install the package + +1. Open **Window → Package Manager**. +2. Select **+**, then **Add package from git URL**. +3. Enter `https://github.com/wallstop/DataVisualizer.git` and select **Add**. + +Unity imports the package, and the menu item appears. + +## Open the window + +Select **Tools → Wallstop Studios → Data Visualizer**, then dock it next to the +Inspector. The window has three panels: + +- **Namespaces and types** (left) lists your ScriptableObject types by C# + namespace. Expand a namespace, then select a type to load its instances. +- **Objects** (center) lists every instance of the selected type. One row is + selected at a time. +- **Inspector** (right) shows the full inspector for the selected asset, + including custom editors and Odin Inspector integration. + +## Add your types + +Three controls above the namespace panel fill the catalog: + +- **Search Types** queries the ScriptableObject types Unity knows about. Add + single types or a whole namespace in one action. +- **Scan Asset Folder** crawls a folder and registers the types and existing + assets it finds. Use it to adopt a project that already has data. +- **Scan Scripts Folder** registers types from source folders, which is the + right choice before you create any assets. + +Removing a type or namespace only stops Data Visualizer from tracking it. Your +assets stay on disk. + +## Edit your first asset + +1. Select a type in the left panel. +2. Select an instance in the middle panel. +3. Edit a field in the right panel. The change saves immediately, as in Unity's + own Inspector. + +Use the buttons above the object list to manage the selection: **Clone** writes +a copy with a `Clone` suffix beside the original, **Rename** renames the asset +on disk, **Move** retargets it to another folder, and **Delete** removes it +after you confirm. + +## Create an asset + +Select **Create** to add a new instance of the active type. The asset lands in +your **Data Folder**, which you set under **Settings**. Clones always stay +beside their original, whatever the Data Folder is. + +## Filter and search + +- The filter field above the namespace list narrows the type rows by display + name, without case sensitivity. Namespace headers stay visible. +- The search box above the object list searches the text of all loaded + instances, so you can jump to an asset by its contents. +- Drag labels from **Available** into the **AND:** or **OR:** rows to show only + assets that carry them. The **AND &&** and **OR ||** toggle switches between + matching every dragged label and matching any of them. + +## Keep your arrangement + +Pane widths, ordering, tracked types, and your selection persist between +sessions. Under **Settings**, choose where that state lives: + +- **Persist state in user settings** (default) stores it in your local user + cache, so each developer keeps a private arrangement and version control + stays free of layout churn. +- The project settings asset stores the state in the repository, which suits a + team that wants one shared arrangement. + +**Select active object** mirrors your Data Visualizer selection in Unity's +Inspector, and **Data Folder** sets where new assets are created. + +## While the editor is playing + +Entering Play Mode pauses package-driven work. The window keeps the last loaded +view and shows *Package editing is paused during Play Mode*. While it is paused: + +- **Create**, the add-type controls, and the inspector are disabled, and the + inspector shows read-only content. +- Asset operations are refused: clone, rename, move, delete, and label changes. +- Selecting another type does not load its instances, and the catalog does not + change. + +Everything resumes when you leave Play Mode. + +## Next steps + +- Themes: pick Classic, Nord, Dracula, Compact, or Minimal under **Settings → + Theme**, or author your own. +- Extensibility: derive from `BaseDataObject`, implement the lifecycle + interfaces, and return your own UI Toolkit content from `BuildGUI`. +- The [README](https://github.com/wallstop/DataVisualizer#readme) documents + every control, the theme tokens, and the runtime extension points. diff --git a/docs/getting-started.md.meta b/docs/getting-started.md.meta new file mode 100644 index 0000000..89ab76a --- /dev/null +++ b/docs/getting-started.md.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 589cb90f42e24feb8ff12b557aebc411 +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..f5955bb --- /dev/null +++ b/docs/index.md @@ -0,0 +1,48 @@ +# Data Visualizer + +Data Visualizer is a Unity editor window for ScriptableObject-heavy projects. +It gathers your data in one place: a catalog of your ScriptableObject types +organized by namespace, a list of every instance of the selected type, and the +inspector for the asset you picked. You edit, reorder, and batch-process data +without moving between the Project panel and the Inspector. + +Data Visualizer is free forever. There are no subscriptions, no paid upgrades, +and no feature-gated tiers. The full source is MIT-licensed, and every +capability described here ships in the free package. + +## What it does + +| Task | What the window gives you | +| --- | --- | +| Find | Search your ScriptableObject types by name, add a whole namespace at once, or scan a folder for assets you already have. | +| Inspect | Edit the selected asset in the inspector you already know, including custom editors and Odin Inspector integration. | +| Organize | Reorder namespaces, types, and instances, filter by label, and keep the arrangement across sessions. | +| Edit | Clone, rename, move, delete, and create assets in place, then run processors over one type or all of its instances. | + +## Requirements + +- Unity 2021.3 or newer, the floor declared in `package.json`. The window is + developed and tested on Unity 6. +- No additional package dependencies. + +## Install + +1. Open **Window → Package Manager**. +2. Select **+**, then **Add package from git URL**. +3. Enter `https://github.com/wallstop/DataVisualizer.git` and select **Add**. + +Open the window with **Tools → Wallstop Studios → Data Visualizer**. +[Getting started](getting-started.md) walks through the first session. + +!!! note + + Data Visualizer is alpha software. It was built for wallstop studios' + internal use, there is no support commitment yet, and breaking changes can + arrive in any release. Feel free to use it, and report what breaks. + +## Where to go next + +- [Getting started](getting-started.md) — install the package, open the window, and edit your first asset. +- [README](https://github.com/wallstop/DataVisualizer#readme) — the full feature guide, including themes, search, filtering, persistence, and extension points. +- [Video walkthrough](https://youtu.be/3oUxUSKNyhw) — a visual tour of the window. +- [Issues](https://github.com/wallstop/DataVisualizer/issues) — known problems and feature requests. diff --git a/docs/index.md.meta b/docs/index.md.meta new file mode 100644 index 0000000..3d322d8 --- /dev/null +++ b/docs/index.md.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: ffe4f2f457124c0e9169d55dc713fe70 +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/mkdocs.yml b/mkdocs.yml new file mode 100644 index 0000000..621756f --- /dev/null +++ b/mkdocs.yml @@ -0,0 +1,56 @@ +# Documentation site configuration. The site is built into site/, which is +# gitignored and excluded from the UPM and npm package payloads. +site_name: Data Visualizer +site_description: Find, inspect, organize, and edit ScriptableObject data in one Unity editor window. +site_url: https://wallstop.github.io/DataVisualizer/ +repo_url: https://github.com/wallstop/DataVisualizer +edit_uri: edit/main/docs/ + +docs_dir: docs +site_dir: site +strict: true + +# admonition backs the `!!! note` callouts; tables and fenced code are already +# MkDocs defaults. +markdown_extensions: + - admonition + +# The README screenshots stay out of the site until the offscreen capture path +# paints every element (issue #114). The README links to them from +# raw.githubusercontent.com, so they stay in the repository. Unity's .meta +# companions are importer bookkeeping and must not be published either. +exclude_docs: | + images/ + *.meta + +theme: + name: material + features: + - navigation.sections + - navigation.top + - search.highlight + - search.suggest + palette: + - media: "(prefers-color-scheme: light)" + scheme: default + toggle: + icon: material/brightness-7 + name: Switch to dark mode + - media: "(prefers-color-scheme: dark)" + scheme: slate + toggle: + icon: material/brightness-4 + name: Switch to light mode + +nav: + - Home: index.md + - Getting started: getting-started.md + +validation: + nav: + omitted_files: warn + not_found: warn + links: + not_found: warn + absolute_links: relative_to_docs + unrecognized_links: warn diff --git a/mkdocs.yml.meta b/mkdocs.yml.meta new file mode 100644 index 0000000..c89c477 --- /dev/null +++ b/mkdocs.yml.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: f36939d2efcb4b4196601f9c5d9b7e00 +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/requirements-docs.txt b/requirements-docs.txt new file mode 100644 index 0000000..eb1ae71 --- /dev/null +++ b/requirements-docs.txt @@ -0,0 +1,4 @@ +# Documentation build dependencies. Development only: neither this file nor +# the built site is part of the UPM or npm package payload. +mkdocs==1.6.1 +mkdocs-material==9.7.7 diff --git a/requirements-docs.txt.meta b/requirements-docs.txt.meta new file mode 100644 index 0000000..fc64da4 --- /dev/null +++ b/requirements-docs.txt.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 2449b5a8cbac4619bf44f39f1c0af427 +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/scripts/build-docs.ps1 b/scripts/build-docs.ps1 new file mode 100644 index 0000000..7a71f79 --- /dev/null +++ b/scripts/build-docs.ps1 @@ -0,0 +1,92 @@ +[CmdletBinding(PositionalBinding = $false)] +param( + [string]$Root, + [switch]$Serve +) + +$ErrorActionPreference = 'Stop' +Import-Module (Join-Path $PSScriptRoot 'LlmConfig.psm1') -DisableNameChecking + +function Resolve-PythonLauncher { + foreach ($candidate in @('python3', 'python')) { + $command = Get-Command $candidate -ErrorAction SilentlyContinue + if ($null -ne $command) { + return $command.Source + } + } + return $null +} + +function Get-VenvPythonPath { + param([string]$VenvRoot) + $relative = if ($IsWindows) { 'Scripts/python.exe' } else { 'bin/python' } + return Join-Path $VenvRoot $relative +} + +function Invoke-CheckedPython { + param( + [string]$PythonPath, + [string[]]$Arguments, + [string]$Description + ) + & $PythonPath @Arguments + if ($LASTEXITCODE -ne 0) { + throw "$Description failed with exit code $LASTEXITCODE" + } +} + +try { + $repoRoot = Resolve-LlmRoot $Root + $requirementsPath = Join-Path $repoRoot 'requirements-docs.txt' + $configPath = Join-Path $repoRoot 'mkdocs.yml' + foreach ($required in @($requirementsPath, $configPath)) { + if (-not (Test-Path -LiteralPath $required -PathType Leaf)) { + Write-Host "[docs] ERROR: required file '$required' does not exist" + exit 1 + } + } + + $venvRoot = Join-Path $repoRoot '.docs-venv' + $pythonPath = Get-VenvPythonPath -VenvRoot $venvRoot + if (-not (Test-Path -LiteralPath $pythonPath -PathType Leaf)) { + $launcher = Resolve-PythonLauncher + if ([string]::IsNullOrEmpty($launcher)) { + Write-Host '[docs] ERROR: no python3 or python executable is on PATH' + exit 1 + } + Write-Host "[docs] creating the virtual environment at $venvRoot" + Invoke-CheckedPython -PythonPath $launcher -Arguments @( + '-m', 'venv', $venvRoot + ) -Description 'python -m venv' + $pythonPath = Get-VenvPythonPath -VenvRoot $venvRoot + if (-not (Test-Path -LiteralPath $pythonPath -PathType Leaf)) { + Write-Host "[docs] ERROR: the virtual environment has no interpreter at $pythonPath" + exit 1 + } + } + + Invoke-CheckedPython -PythonPath $pythonPath -Arguments @( + '-m', 'pip', 'install', '--quiet', '--disable-pip-version-check', + '--requirement', $requirementsPath + ) -Description 'pip install -r requirements-docs.txt' + + if ($Serve) { + Invoke-CheckedPython -PythonPath $pythonPath -Arguments @( + '-m', 'mkdocs', 'serve', '--config-file', $configPath + ) -Description 'mkdocs serve' + exit 0 + } + + # --strict is repeated on the command line so the build stays strict even + # if mkdocs.yml is later relaxed for the preview server. The output + # directory stays owned by mkdocs.yml. + Invoke-CheckedPython -PythonPath $pythonPath -Arguments @( + '-m', 'mkdocs', 'build', '--strict', '--config-file', $configPath + ) -Description 'mkdocs build --strict' + + Write-Host '[docs] OK: strict build succeeded' + exit 0 +} catch { + Write-Host "[docs] ERROR: $($_.Exception.Message)" + exit 1 +} diff --git a/scripts/build-docs.ps1.meta b/scripts/build-docs.ps1.meta new file mode 100644 index 0000000..a05743f --- /dev/null +++ b/scripts/build-docs.ps1.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 1da09103451b49238a243a79b34261a4 +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: