feat: publish HoloLake model-native living system source
This commit is contained in:
parent
6ad10edde1
commit
c395dd3a99
2467 changed files with 615073 additions and 0 deletions
|
|
@ -0,0 +1,100 @@
|
|||
import { describe, expect, it } from 'vitest'
|
||||
import type { ViewDefinition, ViewFile } from '../types'
|
||||
import { makeEntry } from '../test-utils/noteListTestUtils'
|
||||
import { collectionFromSelection } from './collectionFromSelection'
|
||||
|
||||
function emptyFilters(): ViewDefinition['filters'] {
|
||||
return { all: [] }
|
||||
}
|
||||
|
||||
function makeView(definition: Partial<ViewDefinition> = {}): ViewFile {
|
||||
return {
|
||||
filename: 'active-projects.yml',
|
||||
definition: {
|
||||
name: 'Active Projects',
|
||||
icon: null,
|
||||
color: null,
|
||||
sort: 'modified:desc',
|
||||
listPropertiesDisplay: ['status'],
|
||||
filters: emptyFilters(),
|
||||
...definition,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
describe('collectionFromSelection', () => {
|
||||
it('maps built-in filters to list-presented collections', () => {
|
||||
const collection = collectionFromSelection({ kind: 'filter', filter: 'all' })
|
||||
|
||||
expect(collection).toMatchObject({
|
||||
id: 'builtin:all',
|
||||
label: 'All Notes',
|
||||
origin: 'builtin',
|
||||
presentation: { type: 'list', sort: null, properties: [] },
|
||||
})
|
||||
})
|
||||
|
||||
it('maps type and folder selections without requiring saved YAML', () => {
|
||||
expect(collectionFromSelection({ kind: 'sectionGroup', type: 'Project' })).toMatchObject({
|
||||
id: 'type:Project',
|
||||
label: 'Project',
|
||||
origin: 'type',
|
||||
})
|
||||
|
||||
expect(collectionFromSelection({ kind: 'folder', path: 'clients', rootPath: '/vault' })).toMatchObject({
|
||||
id: 'folder:/vault:clients',
|
||||
label: 'clients',
|
||||
origin: 'folder',
|
||||
})
|
||||
})
|
||||
|
||||
it('maps neighborhood selections to a collection around the source note', () => {
|
||||
const entry = makeEntry({ path: '/vault/alpha.md', title: 'Alpha' })
|
||||
const collection = collectionFromSelection({ kind: 'entity', entry })
|
||||
|
||||
expect(collection).toMatchObject({
|
||||
id: 'neighborhood:/vault/alpha.md',
|
||||
label: 'Alpha',
|
||||
origin: 'neighborhood',
|
||||
entry,
|
||||
})
|
||||
})
|
||||
|
||||
it('normalizes legacy saved-view list settings into presentation config', () => {
|
||||
const view = makeView()
|
||||
const collection = collectionFromSelection(
|
||||
{ kind: 'view', filename: view.filename },
|
||||
{ views: [view] },
|
||||
)
|
||||
|
||||
expect(collection).toMatchObject({
|
||||
id: 'saved-view::active-projects.yml',
|
||||
label: 'Active Projects',
|
||||
origin: 'saved-view',
|
||||
filter: view.definition.filters,
|
||||
presentation: { type: 'list', sort: 'modified:desc', properties: ['status'] },
|
||||
view,
|
||||
})
|
||||
})
|
||||
|
||||
it('lets nested list presentation config override legacy saved-view fields in memory', () => {
|
||||
const view = makeView({
|
||||
presentation: {
|
||||
type: 'list',
|
||||
sort: 'title:asc',
|
||||
properties: ['Owner'],
|
||||
},
|
||||
} as Partial<ViewDefinition>)
|
||||
|
||||
const collection = collectionFromSelection(
|
||||
{ kind: 'view', filename: view.filename },
|
||||
{ views: [view] },
|
||||
)
|
||||
|
||||
expect(collection.presentation).toEqual({
|
||||
type: 'list',
|
||||
sort: 'title:asc',
|
||||
properties: ['Owner'],
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
@ -0,0 +1,88 @@
|
|||
import type { SidebarFilter, SidebarSelection, ViewFile } from '../types'
|
||||
import { viewMatchesSelection } from '../utils/viewIdentity'
|
||||
import { defaultListPresentation, presentationFromViewDefinition } from './presentationConfig'
|
||||
import type { CollectionDefinition } from './collectionTypes'
|
||||
|
||||
interface CollectionContext {
|
||||
views?: ViewFile[]
|
||||
}
|
||||
|
||||
const BUILTIN_LABELS: Record<SidebarFilter, string> = {
|
||||
all: 'All Notes',
|
||||
archived: 'Archived',
|
||||
changes: 'Changes',
|
||||
pulse: 'Pulse',
|
||||
inbox: 'Inbox',
|
||||
favorites: 'Favorites',
|
||||
}
|
||||
|
||||
function folderLabel(selection: Extract<SidebarSelection, { kind: 'folder' }>): string {
|
||||
return selection.path || 'Vault'
|
||||
}
|
||||
|
||||
function identityPart(value: string | undefined): string {
|
||||
return value ?? ''
|
||||
}
|
||||
|
||||
function selectedView(selection: SidebarSelection, views?: ViewFile[]): ViewFile | undefined {
|
||||
return selection.kind === 'view'
|
||||
? views?.find((view) => viewMatchesSelection(view, selection))
|
||||
: undefined
|
||||
}
|
||||
|
||||
export function collectionFromSelection(
|
||||
selection: SidebarSelection,
|
||||
context: CollectionContext = {},
|
||||
): CollectionDefinition {
|
||||
if (selection.kind === 'filter') {
|
||||
return {
|
||||
id: `builtin:${selection.filter}`,
|
||||
label: BUILTIN_LABELS[selection.filter],
|
||||
origin: 'builtin',
|
||||
selection,
|
||||
presentation: defaultListPresentation(),
|
||||
}
|
||||
}
|
||||
|
||||
if (selection.kind === 'sectionGroup') {
|
||||
return {
|
||||
id: `type:${selection.type}`,
|
||||
label: selection.type,
|
||||
origin: 'type',
|
||||
selection,
|
||||
presentation: defaultListPresentation(),
|
||||
}
|
||||
}
|
||||
|
||||
if (selection.kind === 'folder') {
|
||||
return {
|
||||
id: `folder:${identityPart(selection.rootPath)}:${selection.path}`,
|
||||
label: folderLabel(selection),
|
||||
origin: 'folder',
|
||||
selection,
|
||||
presentation: defaultListPresentation(),
|
||||
}
|
||||
}
|
||||
|
||||
if (selection.kind === 'entity') {
|
||||
return {
|
||||
id: `neighborhood:${selection.entry.path}`,
|
||||
label: selection.entry.title,
|
||||
origin: 'neighborhood',
|
||||
selection,
|
||||
presentation: defaultListPresentation(),
|
||||
entry: selection.entry,
|
||||
}
|
||||
}
|
||||
|
||||
const view = selectedView(selection, context.views)
|
||||
return {
|
||||
id: `saved-view:${identityPart(selection.rootPath)}:${selection.filename}`,
|
||||
label: view?.definition.name ?? selection.filename,
|
||||
origin: 'saved-view',
|
||||
selection,
|
||||
presentation: view ? presentationFromViewDefinition(view.definition) : defaultListPresentation(),
|
||||
filter: view?.definition.filters,
|
||||
view,
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
import type { FilterGroup, SidebarSelection, VaultEntry, ViewFile } from '../types'
|
||||
|
||||
export const COLLECTION_PRESENTATION_LIST = 'list'
|
||||
|
||||
export type CollectionPresentationType = typeof COLLECTION_PRESENTATION_LIST
|
||||
|
||||
export interface ListCollectionPresentationConfig {
|
||||
type: typeof COLLECTION_PRESENTATION_LIST
|
||||
sort: string | null
|
||||
properties: string[]
|
||||
}
|
||||
|
||||
export type CollectionPresentationConfig = ListCollectionPresentationConfig
|
||||
|
||||
export type CollectionOrigin = 'builtin' | 'type' | 'folder' | 'saved-view' | 'neighborhood'
|
||||
|
||||
export interface CollectionDefinition {
|
||||
id: string
|
||||
label: string
|
||||
origin: CollectionOrigin
|
||||
selection: SidebarSelection
|
||||
presentation: CollectionPresentationConfig
|
||||
filter?: FilterGroup
|
||||
entry?: VaultEntry
|
||||
view?: ViewFile
|
||||
}
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
import type { ViewDefinition } from '../types'
|
||||
import {
|
||||
COLLECTION_PRESENTATION_LIST,
|
||||
type CollectionPresentationConfig,
|
||||
type ListCollectionPresentationConfig,
|
||||
} from './collectionTypes'
|
||||
|
||||
type UnknownRecord = Record<string, unknown>
|
||||
|
||||
function isRecord(value: unknown): value is UnknownRecord {
|
||||
return typeof value === 'object' && value !== null && !Array.isArray(value)
|
||||
}
|
||||
|
||||
function nullableString(value: unknown): string | null {
|
||||
return typeof value === 'string' && value.trim().length > 0 ? value : null
|
||||
}
|
||||
|
||||
function stringArray(value: unknown): string[] {
|
||||
return Array.isArray(value) ? value.filter((item): item is string => typeof item === 'string') : []
|
||||
}
|
||||
|
||||
function listPresentation(source: UnknownRecord, fallback: ListCollectionPresentationConfig): ListCollectionPresentationConfig {
|
||||
return {
|
||||
type: COLLECTION_PRESENTATION_LIST,
|
||||
sort: nullableString(source.sort) ?? fallback.sort,
|
||||
properties: stringArray(source.properties).length > 0 ? stringArray(source.properties) : fallback.properties,
|
||||
}
|
||||
}
|
||||
|
||||
export function defaultListPresentation(): ListCollectionPresentationConfig {
|
||||
return { type: COLLECTION_PRESENTATION_LIST, sort: null, properties: [] }
|
||||
}
|
||||
|
||||
export function presentationFromViewDefinition(definition: ViewDefinition): CollectionPresentationConfig {
|
||||
const fallback: ListCollectionPresentationConfig = {
|
||||
type: COLLECTION_PRESENTATION_LIST,
|
||||
sort: nullableString(definition.sort),
|
||||
properties: stringArray(definition.listPropertiesDisplay),
|
||||
}
|
||||
const rawPresentation = Reflect.get(definition as unknown as UnknownRecord, 'presentation')
|
||||
if (!isRecord(rawPresentation)) return fallback
|
||||
if (rawPresentation.type !== COLLECTION_PRESENTATION_LIST) return fallback
|
||||
return listPresentation(rawPresentation, fallback)
|
||||
}
|
||||
|
|
@ -0,0 +1,80 @@
|
|||
import { describe, expect, it } from 'vitest'
|
||||
import type { ViewFile } from '../types'
|
||||
import { makeEntry } from '../test-utils/noteListTestUtils'
|
||||
import { collectionFromSelection } from './collectionFromSelection'
|
||||
import { resolveCollectionEntries } from './resolveCollectionEntries'
|
||||
|
||||
describe('resolveCollectionEntries', () => {
|
||||
it('resolves ordinary collections through current note-list filtering', () => {
|
||||
const entries = [
|
||||
makeEntry({ path: '/vault/alpha.md', title: 'Alpha', isA: 'Project' }),
|
||||
makeEntry({ path: '/vault/beta.md', title: 'Beta', isA: 'Note' }),
|
||||
]
|
||||
const collection = collectionFromSelection({ kind: 'sectionGroup', type: 'Project' })
|
||||
|
||||
const resolved = resolveCollectionEntries(collection, entries)
|
||||
|
||||
expect(resolved.entries.map((entry) => entry.title)).toEqual(['Alpha'])
|
||||
expect(resolved.entityEntry).toBeNull()
|
||||
expect(resolved.relationshipGroups).toEqual([])
|
||||
})
|
||||
|
||||
it('keeps Changes and Inbox entries caller-supplied for existing transient flows', () => {
|
||||
const changes = [makeEntry({ path: '/vault/changed.md', title: 'Changed' })]
|
||||
const inbox = [makeEntry({ path: '/vault/inbox.md', title: 'Inbox' })]
|
||||
|
||||
expect(
|
||||
resolveCollectionEntries(
|
||||
collectionFromSelection({ kind: 'filter', filter: 'changes' }),
|
||||
[],
|
||||
{ changesEntries: changes },
|
||||
).entries,
|
||||
).toBe(changes)
|
||||
|
||||
expect(
|
||||
resolveCollectionEntries(
|
||||
collectionFromSelection({ kind: 'filter', filter: 'inbox' }),
|
||||
[],
|
||||
{ inboxEntries: inbox },
|
||||
).entries,
|
||||
).toBe(inbox)
|
||||
})
|
||||
|
||||
it('resolves saved views using their existing YAML filters', () => {
|
||||
const entries = [
|
||||
makeEntry({ path: '/vault/alpha.md', title: 'Alpha', isA: 'Project' }),
|
||||
makeEntry({ path: '/vault/beta.md', title: 'Beta', isA: 'Note' }),
|
||||
]
|
||||
const view: ViewFile = {
|
||||
filename: 'projects.yml',
|
||||
definition: {
|
||||
name: 'Projects',
|
||||
icon: null,
|
||||
color: null,
|
||||
sort: null,
|
||||
filters: { all: [{ field: 'type', op: 'equals', value: 'Project' }] },
|
||||
},
|
||||
}
|
||||
const collection = collectionFromSelection({ kind: 'view', filename: view.filename }, { views: [view] })
|
||||
|
||||
const resolved = resolveCollectionEntries(collection, entries, { views: [view] })
|
||||
|
||||
expect(resolved.entries.map((entry) => entry.title)).toEqual(['Alpha'])
|
||||
})
|
||||
|
||||
it('resolves neighborhood collections into grouped relationship data', () => {
|
||||
const alpha = makeEntry({
|
||||
path: '/vault/alpha.md',
|
||||
title: 'Alpha',
|
||||
relationships: { related_to: ['[[beta]]'] },
|
||||
})
|
||||
const beta = makeEntry({ path: '/vault/beta.md', filename: 'beta.md', title: 'Beta' })
|
||||
const collection = collectionFromSelection({ kind: 'entity', entry: alpha })
|
||||
|
||||
const resolved = resolveCollectionEntries(collection, [alpha, beta])
|
||||
|
||||
expect(resolved.entries).toEqual([])
|
||||
expect(resolved.entityEntry).toBe(alpha)
|
||||
expect(resolved.relationshipGroups.some((group) => group.entries.includes(beta))).toBe(true)
|
||||
})
|
||||
})
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
import type { VaultEntry } from '../types'
|
||||
import {
|
||||
type FilterEntriesOptions,
|
||||
type RelationshipGroup,
|
||||
buildRelationshipGroups,
|
||||
filterEntries,
|
||||
} from '../utils/noteListHelpers'
|
||||
import type { CollectionDefinition } from './collectionTypes'
|
||||
|
||||
interface ResolveCollectionEntriesOptions extends FilterEntriesOptions {
|
||||
changesEntries?: VaultEntry[]
|
||||
inboxEntries?: VaultEntry[]
|
||||
}
|
||||
|
||||
export interface ResolvedCollectionEntries {
|
||||
entries: VaultEntry[]
|
||||
entityEntry: VaultEntry | null
|
||||
relationshipGroups: RelationshipGroup[]
|
||||
}
|
||||
|
||||
function specialFilterEntries(
|
||||
collection: CollectionDefinition,
|
||||
options: ResolveCollectionEntriesOptions,
|
||||
): VaultEntry[] | null {
|
||||
const selection = collection.selection
|
||||
if (selection.kind !== 'filter') return null
|
||||
if (selection.filter === 'changes') return options.changesEntries ?? []
|
||||
if (selection.filter === 'inbox') return options.inboxEntries ?? []
|
||||
return null
|
||||
}
|
||||
|
||||
function currentEntityEntry(collection: CollectionDefinition, entries: VaultEntry[]): VaultEntry | null {
|
||||
if (collection.selection.kind !== 'entity') return null
|
||||
const selectedEntry = collection.selection.entry
|
||||
return entries.find((entry) => entry.path === selectedEntry.path) ?? selectedEntry
|
||||
}
|
||||
|
||||
export function resolveCollectionEntries(
|
||||
collection: CollectionDefinition,
|
||||
entries: VaultEntry[],
|
||||
options: ResolveCollectionEntriesOptions = {},
|
||||
): ResolvedCollectionEntries {
|
||||
const entityEntry = currentEntityEntry(collection, entries)
|
||||
if (entityEntry) {
|
||||
return {
|
||||
entries: [],
|
||||
entityEntry,
|
||||
relationshipGroups: buildRelationshipGroups(entityEntry, entries),
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
entries: specialFilterEntries(collection, options) ?? filterEntries(entries, collection.selection, options),
|
||||
entityEntry: null,
|
||||
relationshipGroups: [],
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue