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:
co-authored by
Claude Opus 4.6
parent
5077011c26
commit
98c1d5247e
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user