Support conflict types, involvement sizing, and map-type toggles while keeping polar sector layout; improve GraphView init retries and edge matching on network maps. Co-authored-by: Cursor <cursoragent@cursor.com>
137 lines
3.6 KiB
JavaScript
137 lines
3.6 KiB
JavaScript
import { localDb, DEFAULT_MAP_NAME } from '../db/localDb'
|
|
import { generateId } from '../../lib/uuid'
|
|
import { localNetworkMapTypeRepository } from './networkMapTypeRepository.local'
|
|
|
|
function nowIso() {
|
|
return new Date().toISOString()
|
|
}
|
|
|
|
function withDefaults(payload = {}) {
|
|
return {
|
|
name: '',
|
|
description: '',
|
|
mapTypeId: payload.mapTypeId || null,
|
|
conflictSubject: '',
|
|
workspaceId: 'personal',
|
|
...payload,
|
|
}
|
|
}
|
|
|
|
async function findActiveMap(id) {
|
|
const direct = await localDb.networkMaps.get(id)
|
|
if (direct && !direct.deletedAt) return direct
|
|
const sid = String(id)
|
|
const all = await localDb.networkMaps.toArray()
|
|
return all.find((m) => !m.deletedAt && String(m.id) === sid) || null
|
|
}
|
|
|
|
async function membershipsCount(mapId) {
|
|
const all = await localDb.networkMapMemberships.toArray()
|
|
return all.filter((m) => !m.deletedAt && String(m.mapId) === String(mapId)).length
|
|
}
|
|
|
|
async function hydrate(map) {
|
|
if (!map || map.deletedAt) return null
|
|
return {
|
|
...map,
|
|
memberships_count: await membershipsCount(map.id),
|
|
}
|
|
}
|
|
|
|
async function ensureDefaultMap() {
|
|
const all = await localDb.networkMaps.toArray()
|
|
const active = all.filter((m) => !m.deletedAt)
|
|
if (active.length) return active.sort((a, b) => a.name.localeCompare(b.name, 'ru'))[0]
|
|
|
|
const defaultType = await localNetworkMapTypeRepository.getDefault()
|
|
const ts = nowIso()
|
|
const id = generateId()
|
|
await localDb.networkMaps.put({
|
|
id,
|
|
name: DEFAULT_MAP_NAME,
|
|
description: '',
|
|
mapTypeId: defaultType?.id || null,
|
|
workspaceId: 'personal',
|
|
version: 1,
|
|
createdAt: ts,
|
|
updatedAt: ts,
|
|
deletedAt: null,
|
|
})
|
|
return findActiveMap(id)
|
|
}
|
|
|
|
export const localNetworkMapRepository = {
|
|
async list() {
|
|
const all = await localDb.networkMaps.toArray()
|
|
const filtered = all
|
|
.filter((m) => !m.deletedAt)
|
|
.sort((a, b) => a.name.localeCompare(b.name, 'ru'))
|
|
if (!filtered.length) {
|
|
const defaultMap = await ensureDefaultMap()
|
|
return [await hydrate(defaultMap)]
|
|
}
|
|
const hydrated = await Promise.all(filtered.map(hydrate))
|
|
return hydrated.filter(Boolean)
|
|
},
|
|
|
|
async getById(id) {
|
|
const map = await findActiveMap(id)
|
|
return hydrate(map)
|
|
},
|
|
|
|
async create(payload) {
|
|
const defaultType = await localNetworkMapTypeRepository.getDefault()
|
|
const ts = nowIso()
|
|
const record = withDefaults(payload)
|
|
if (!record.mapTypeId) record.mapTypeId = defaultType?.id || null
|
|
const id = generateId()
|
|
await localDb.networkMaps.put({
|
|
...record,
|
|
id,
|
|
version: 1,
|
|
createdAt: ts,
|
|
updatedAt: ts,
|
|
deletedAt: null,
|
|
})
|
|
return this.getById(id)
|
|
},
|
|
|
|
async update(id, payload) {
|
|
const existing = await findActiveMap(id)
|
|
if (!existing) {
|
|
throw new Error('Карта не найдена')
|
|
}
|
|
await localDb.networkMaps.update(existing.id, {
|
|
...payload,
|
|
updatedAt: nowIso(),
|
|
version: Number(existing.version || 1) + 1,
|
|
})
|
|
return this.getById(existing.id)
|
|
},
|
|
|
|
async remove(id) {
|
|
const map = await findActiveMap(id)
|
|
if (!map) {
|
|
throw new Error('Карта не найдена')
|
|
}
|
|
const ts = nowIso()
|
|
await localDb.networkMaps.update(map.id, {
|
|
deletedAt: ts,
|
|
updatedAt: ts,
|
|
version: Number(map.version || 1) + 1,
|
|
})
|
|
const memberships = await localDb.networkMapMemberships
|
|
.filter((m) => !m.deletedAt && String(m.mapId) === String(map.id))
|
|
.toArray()
|
|
await Promise.all(
|
|
memberships.map((m) =>
|
|
localDb.networkMapMemberships.update(m.id, {
|
|
deletedAt: ts,
|
|
updatedAt: ts,
|
|
version: Number(m.version || 1) + 1,
|
|
})
|
|
)
|
|
)
|
|
},
|
|
}
|