feat: filter empty projects from public gallery (#125)

* feat(community): filter empty projects from public gallery

Add is_empty column to projects table so the public gallery only shows
projects with actual content. Scene graph emptiness is computed on save
and on creation, and a backfill migration marks existing non-empty
projects. Also cleans up import ordering, fixes README migration path,
and removes stale feedback table migration.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Use ESNext modules and bundler resolution

Update packages/db tsconfig to emit ESNext modules and use 'bundler' moduleResolution. This enables modern ESM output and bundler-style resolution for the db package (keeps outDir and rootDir unchanged).

* feat(editor): add dark mode theme and grid visibility toggle

Add theme state to viewer store with light/dark modes, dark-aware
lighting and background colors, grid show/hide toggle in settings,
and theme toggle buttons in the sidebar.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Add ground occluder, animated lighting & UI tweaks

Introduce a ground occluder and smoother animated lighting while adjusting editor UI and icons. Viewer: add GroundOccluder (uses polygon-clipping to union slab/zone polygons) and AnimatedBackground, integrate both into the Canvas; improve Lights to interpolate intensities/colors/ambient for smooth theme transitions. Editor: refactor sidebar header to support inline project title editing and move the theme toggle; replace some lucide icons with image assets and add a settings icon (apps/editor/public/icons/settings.png). Also add polygon-clipping to the viewer package dependencies.

* Update dark mode background color and clean up imports

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Aymeric Rabot
2026-02-27 23:49:48 -05:00
committed by GitHub
co-authored by Claude Opus 4.6
parent 5077011c26
commit 98c1d5247e
25 changed files with 2775 additions and 91 deletions
+1 -1
View File
@@ -162,7 +162,7 @@ This feature requires:
- `properties` - Property records
- `properties_addresses` - Property addresses
- `properties_models` - Scene graph models
- Database migrations are managed in `packages/db/supabase/migrations/`
- Database migrations are managed in `supabase/migrations/`
## Development
@@ -10,6 +10,7 @@ import { createServerSupabaseClient } from '../database/server'
import { getSession } from '../auth/server'
import { createId } from '../utils/id-generator'
import type { ActionResult } from '../projects/actions'
import { isSceneGraphEmpty } from './scene-graph-utils'
export interface SceneGraph {
nodes: Record<AnyNodeId, AnyNode>
@@ -166,6 +167,14 @@ export async function saveProjectModel(
}
}
// Determine if scene graph is empty
const isEmpty = isSceneGraphEmpty(sceneGraph)
// Update the project's is_empty flag
await (supabase.from('projects') as any)
.update({ is_empty: isEmpty })
.eq('id', projectId)
// Check if a model already exists
const { data: existingModel } = await supabase
.from('projects_models')
@@ -0,0 +1,25 @@
import type { SceneGraph } from './actions'
const DEFAULT_NODE_TYPES = ['site', 'building', 'level']
export function isSceneGraphEmpty(sceneGraph: SceneGraph | any): boolean {
if (!sceneGraph?.nodes) return true
const nodes = Object.values(sceneGraph.nodes) as any[]
if (nodes.length > 3) {
return false
}
const hasNonDefaultNodes = nodes.some((n) => !DEFAULT_NODE_TYPES.includes(n.type))
if (hasNonDefaultNodes) {
return false
}
const levelNode = nodes.find((n) => n.type === 'level')
if (Array.isArray(levelNode?.children) && levelNode.children.length > 0) {
return false
}
return true
}
@@ -8,7 +8,8 @@
import { createServerSupabaseClient } from '../database/server'
import { getSession } from '../auth/server'
import { createId } from '../utils/id-generator'
import type { CreateProjectParams, Project, Database } from './types'
import { isSceneGraphEmpty } from '../models/scene-graph-utils'
import type { CreateProjectParams, Project } from './types'
export type ActionResult<T = unknown> = {
success: boolean
@@ -250,6 +251,9 @@ export async function createProject(params: CreateProjectParams): Promise<Action
}
}
// Determine if scene graph is empty
const isEmpty = params.sceneGraph ? isSceneGraphEmpty(params.sceneGraph) : true
// Create the project
const projectData = {
id: projectId,
@@ -257,6 +261,7 @@ export async function createProject(params: CreateProjectParams): Promise<Action
address_id: addressId,
owner_id: session.user.id,
is_private: params.isPrivate !== undefined ? params.isPrivate : true,
is_empty: isEmpty,
details_json: params.center
? {
coordinates: params.center,
@@ -401,6 +406,7 @@ export async function getPublicProjects(): Promise<ActionResult<Project[]>> {
owner:auth_users!owner_id(id, name, username, image)
`)
.eq('is_private', false)
.eq('is_empty', false)
.order('views', { ascending: false })
.limit(50)
@@ -13,6 +13,7 @@ export type DbProject = {
created_at: string
updated_at: string
is_private: boolean
is_empty: boolean
show_scans_public: boolean
show_guides_public: boolean
views: number
@@ -106,6 +107,7 @@ export type Project = {
updated_at: string
// Community features
is_private: boolean
is_empty: boolean
show_scans_public: boolean
show_guides_public: boolean
views: number