Initial commit

This commit is contained in:
KCar 2025-08-23 13:04:42 +00:00
commit c3d80f0c61
38 changed files with 9929 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
node_modules

36
Dockerfile Normal file
View File

@ -0,0 +1,36 @@
# syntax=docker/dockerfile:1
# Use a base image with node and build essentials
FROM node:20-buster
# Install dependencies and Bun
RUN apt-get update && \
apt-get install -y curl git unzip && \
curl -fsSL https://bun.sh/install | bash
# Add Bun to PATH
ENV PATH="/root/.bun/bin:${PATH}"
# Set working directory
WORKDIR /app
# Copy package files first
COPY package*.json ./
# Install dependencies using bun
RUN bun install
# Copy the rest of the application code
COPY . .
# Create logs directory
RUN mkdir -p /app/logs && chmod 777 /app/logs
# Expose port 5002 explicitly
EXPOSE 5002
# Set environment variables
ENV LOG_PATH=/app/logs
# Use npm/bun scripts to run the server
CMD ["bun", "run", "dev-server-bun"]

5
README.md Normal file
View File

@ -0,0 +1,5 @@
# Node/Bun server example
This is a simple example of how to integrate tldraw's sync engine with a Node server or a Bun server.
Run `yarn dev-node` or `yarn dev-bun` in this folder to start the server + client.

0
bunfig.toml Normal file
View File

39
package.json Normal file
View File

@ -0,0 +1,39 @@
{
"scripts": {
"dev-server-bun": "bun --watch ./src/server/server.bun.ts"
},
"dependencies": {
"@dagrejs/dagre": "^1.1.4",
"@fastify/cors": "^9.0.1",
"@fastify/websocket": "^10.0.1",
"@tldraw/sync": "3.6.1",
"@tldraw/sync-core": "3.6.1",
"@tldraw/tlschema": "3.6.1",
"@vitejs/plugin-react-swc": "^3.7.0",
"axios": "^1.7.2",
"cheerio": "^1.0.0-rc.12",
"crypto-browserify": "^3.12.0",
"fastify": "^4.28.1",
"itty-router": "^5.0.17",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router-dom": "^6.24.1",
"socket.io-client": "^4.8.0",
"unfurl.js": "^6.4.0",
"vite": "^5.3.3",
"ws": "^8.16.0"
},
"devDependencies": {
"@types/bun": "^1.1.6",
"@types/express": "^4.17.21",
"@types/node": "^20.11.0",
"@types/ws": "^8.5.10",
"concurrently": "^8.2.2",
"lazyrepo": "0.0.0-alpha.27",
"@types/react": "^18.2.0",
"@types/react-dom": "^18.2.0",
"@babel/core": "^7.23.3",
"@babel/preset-react": "^7.23.3",
"typescript": "^5.3.3"
}
}

BIN
src/.DS_Store vendored Normal file

Binary file not shown.

2
src/axiosConfig.ts Normal file
View File

@ -0,0 +1,2 @@
import axios from 'axios';
export default axios;

46
src/logger.ts Normal file
View File

@ -0,0 +1,46 @@
import { existsSync, mkdirSync } from 'fs';
import { join } from 'path';
const LOG_DIR = process.env.LOG_PATH || './logs';
const LOG_FILE = join(LOG_DIR, 'server.log');
// Ensure log directory exists
if (!existsSync(LOG_DIR)) {
mkdirSync(LOG_DIR, { recursive: true });
}
type LogLevel = 'INFO' | 'ERROR' | 'DEBUG' | 'WARN';
function formatLog(level: LogLevel, message: string, data?: any): string {
const timestamp = new Date().toISOString();
const dataStr = data ? `\n${JSON.stringify(data, null, 2)}` : '';
return `[${timestamp}] [${level}] ${message}${dataStr}\n`;
}
export const logger = {
info: (message: string, data?: any) => {
const log = formatLog('INFO', message, data);
console.log(log);
Bun.write(LOG_FILE, log, { append: true });
},
error: (message: string, data?: any) => {
const log = formatLog('ERROR', message, data);
console.error(log);
Bun.write(LOG_FILE, log, { append: true });
},
debug: (message: string, data?: any) => {
if (process.env.NODE_ENV === 'development') {
const log = formatLog('DEBUG', message, data);
console.debug(log);
Bun.write(LOG_FILE, log, { append: true });
}
},
warn: (message: string, data?: any) => {
const log = formatLog('WARN', message, data);
console.warn(log);
Bun.write(LOG_FILE, log, { append: true });
}
};

53
src/server/assets.ts Normal file
View File

@ -0,0 +1,53 @@
import { mkdir, readFile, writeFile } from 'fs/promises'
import { join, resolve } from 'path'
import { Readable } from 'stream'
import crypto from 'crypto'
// We are just using the filesystem to store assets
const DIR = resolve('./.assets')
function hashFileName(fileName: string): string {
return crypto.createHash('md5').update(fileName).digest('hex')
}
export async function storeAsset(id: string, stream: Readable) {
console.log(`Storing asset with ID: ${id}`)
console.log(`Asset directory: ${DIR}`)
try {
await mkdir(DIR, { recursive: true })
console.log(`Directory created or already exists: ${DIR}`)
const hashedFileName = hashFileName(id)
const filePath = join(DIR, hashedFileName)
console.log(`Writing file to path: ${filePath}`)
const chunks = []
for await (const chunk of stream) {
chunks.push(chunk)
}
const buffer = Buffer.concat(chunks)
await writeFile(filePath, buffer)
console.log(`Asset stored successfully with ID: ${id}`)
} catch (error) {
console.error(`Error storing asset with ID: ${id}`, error)
throw error
}
}
export async function loadAsset(id: string) {
console.log(`Loading asset with ID: ${id}`)
const hashedFileName = hashFileName(id)
const filePath = join(DIR, hashedFileName)
console.log(`Reading file from path: ${filePath}`)
try {
const data = await readFile(filePath)
console.log(`Asset loaded successfully with ID: ${id}`)
return data
} catch (error) {
console.error(`Error loading asset with ID: ${id}`, error)
throw error
}
}

121
src/server/rooms.ts Normal file
View File

@ -0,0 +1,121 @@
import { RoomSnapshot, TLSocketRoom } from '@tldraw/sync-core'
import { TLStoreSchema } from '@tldraw/tlschema'
import { mkdir, readFile, writeFile } from 'fs/promises'
import { join } from 'path'
import { TLSchema, TLStore, TLStoreOptions } from 'tldraw'
import { logger } from './../logger'
// For this example we're just saving data to the local filesystem
const DIR = './.rooms'
async function readSnapshotIfExists(roomId: string) {
try {
const data = await readFile(join(DIR, roomId))
const snapshot = JSON.parse(data.toString()) ?? undefined;
logger.info(`📥 Loaded snapshot for room: ${roomId}`);
return snapshot;
} catch (e) {
logger.warn(`⚠️ No existing snapshot found for room: ${roomId}`);
return undefined;
}
}
async function saveSnapshot(roomId: string, snapshot: RoomSnapshot) {
try {
await mkdir(DIR, { recursive: true });
await writeFile(join(DIR, roomId), JSON.stringify(snapshot));
logger.info(`💾 Saved snapshot for room: ${roomId}`);
} catch (error) {
logger.error(`❌ Failed to save snapshot for room: ${roomId}`);
}
}
// We'll keep an in-memory map of rooms and their data
interface RoomState {
room: TLSocketRoom<any, void>
id: string
needsPersist: boolean
lastActivity: number
connectedSessions: Set<string>
}
const rooms = new Map<string, RoomState>()
// Very simple mutex using promise chaining, to avoid race conditions
// when loading rooms. In production you probably want one mutex per room
// to avoid unnecessary blocking!
let mutex = Promise.resolve<null | Error>(null)
export async function makeOrLoadRoom(
roomId: string,
schema: TLSchema,
options?: Partial<TLStoreOptions>
): Promise<TLSocketRoom<any, void>> {
mutex = mutex
.then(async () => {
if (rooms.has(roomId)) {
const roomState = await rooms.get(roomId)!
if (!roomState.room.isClosed()) {
logger.info(`🔄 Using existing room: ${roomId}`);
return null // all good
}
}
logger.info(`🔄 Loading room: ${roomId}`);
const initialSnapshot = await readSnapshotIfExists(roomId)
const roomState: RoomState = {
needsPersist: false,
id: roomId,
lastActivity: Date.now(),
connectedSessions: new Set(),
room: new TLSocketRoom({
initialSnapshot,
schema: schema,
onSessionRemoved(room, args) {
logger.info(`👋 Client disconnected from room: ${roomId}`);
roomState.connectedSessions.delete(args.sessionId);
roomState.lastActivity = Date.now();
if (args.numSessionsRemaining === 0) {
logger.info(`🔒 Closing empty room: ${roomId}`);
room.close();
}
},
onDataChange() {
roomState.needsPersist = true;
roomState.lastActivity = Date.now();
}
}),
}
rooms.set(roomId, roomState)
return null // all good
})
.catch((error) => {
logger.error(`❌ Error making/loading room: ${roomId}`);
// return errors as normal values to avoid stopping the mutex chain
return error
})
const err = await mutex
if (err) throw err
return rooms.get(roomId)!.room
}
// Do persistence on a regular interval.
// In production you probably want a smarter system with throttling.
setInterval(() => {
const now = Date.now();
for (const roomState of rooms.values()) {
if (roomState.needsPersist) {
// persist room
roomState.needsPersist = false;
logger.info(`💾 Saving snapshot for room: ${roomState.id}`);
saveSnapshot(roomState.id, roomState.room.getCurrentSnapshot());
}
if (roomState.room.isClosed()) {
logger.info(`🗑️ Deleting closed room: ${roomState.id}`);
rooms.delete(roomState.id);
} else if (now - roomState.lastActivity > 30 * 60 * 1000) { // 30 minutes inactivity
logger.info(`⏰ Closing inactive room: ${roomState.id}`);
roomState.room.close();
}
}
}, 2000)

199
src/server/schema.ts Normal file
View File

@ -0,0 +1,199 @@
import { createTLSchema, defaultShapeSchemas, defaultBindingSchemas } from '@tldraw/tlschema'
import { ccBindingProps, ccShapeProps } from '../utils/tldraw/cc-base/cc-props'
import { ccBindingMigrations, ccShapeMigrations } from '../utils/tldraw/cc-base/cc-migrations'
import { ccGraphShapeProps } from '../utils/tldraw/cc-base/cc-graph-props'
import { ccGraphMigrations } from '../utils/tldraw/cc-base/cc-graph-migrations'
export const server_schema_default = createTLSchema({
shapes: {
...defaultShapeSchemas,
'cc-base': {
props: ccShapeProps.base,
migrations: ccShapeMigrations.base,
},
'cc-live-transcription': {
props: ccShapeProps.liveTranscription,
migrations: ccShapeMigrations.liveTranscription,
},
'cc-calendar': {
props: ccShapeProps.calendar,
migrations: ccShapeMigrations.calendar,
},
'cc-settings': {
props: ccShapeProps.settings,
migrations: ccShapeMigrations.settings,
},
'cc-slideshow': {
props: ccShapeProps.slideshow,
migrations: ccShapeMigrations.slideshow,
},
'cc-slide': {
props: ccShapeProps.slide,
migrations: ccShapeMigrations.slide,
},
'cc-web-browser': {
props: ccShapeProps.webBrowser,
migrations: ccShapeMigrations.webBrowser,
},
'cc-search': {
props: ccShapeProps.search,
migrations: ccShapeMigrations.search,
},
'cc-youtube-embed': {
props: ccShapeProps['cc-youtube-embed'],
migrations: ccShapeMigrations['cc-youtube-embed'],
},
// Graph shapes
'cc-user-node': {
props: ccGraphShapeProps['cc-user-node'],
migrations: ccGraphMigrations['cc-user-node'],
},
'cc-teacher-node': {
props: ccGraphShapeProps['cc-teacher-node'],
migrations: ccGraphMigrations['cc-teacher-node'],
},
'cc-student-node': {
props: ccGraphShapeProps['cc-student-node'],
migrations: ccGraphMigrations['cc-student-node'],
},
'cc-calendar-node': {
props: ccGraphShapeProps['cc-calendar-node'],
migrations: ccGraphMigrations['cc-calendar-node'],
},
'cc-calendar-year-node': {
props: ccGraphShapeProps['cc-calendar-year-node'],
migrations: ccGraphMigrations['cc-calendar-year-node'],
},
'cc-calendar-month-node': {
props: ccGraphShapeProps['cc-calendar-month-node'],
migrations: ccGraphMigrations['cc-calendar-month-node'],
},
'cc-calendar-week-node': {
props: ccGraphShapeProps['cc-calendar-week-node'],
migrations: ccGraphMigrations['cc-calendar-week-node'],
},
'cc-calendar-day-node': {
props: ccGraphShapeProps['cc-calendar-day-node'],
migrations: ccGraphMigrations['cc-calendar-day-node'],
},
'cc-calendar-time-chunk-node': {
props: ccGraphShapeProps['cc-calendar-time-chunk-node'],
migrations: ccGraphMigrations['cc-calendar-time-chunk-node'],
},
'cc-school-node': {
props: ccGraphShapeProps['cc-school-node'],
migrations: ccGraphMigrations['cc-school-node'],
},
'cc-department-node': {
props: ccGraphShapeProps['cc-department-node'],
migrations: ccGraphMigrations['cc-department-node'],
},
'cc-room-node': {
props: ccGraphShapeProps['cc-room-node'],
migrations: ccGraphMigrations['cc-room-node'],
},
'cc-subject-class-node': {
props: ccGraphShapeProps['cc-subject-class-node'],
migrations: ccGraphMigrations['cc-subject-class-node'],
},
'cc-pastoral-structure-node': {
props: ccGraphShapeProps['cc-pastoral-structure-node'],
migrations: ccGraphMigrations['cc-pastoral-structure-node'],
},
'cc-year-group-node': {
props: ccGraphShapeProps['cc-year-group-node'],
migrations: ccGraphMigrations['cc-year-group-node'],
},
'cc-curriculum-structure-node': {
props: ccGraphShapeProps['cc-curriculum-structure-node'],
migrations: ccGraphMigrations['cc-curriculum-structure-node'],
},
'cc-key-stage-node': {
props: ccGraphShapeProps['cc-key-stage-node'],
migrations: ccGraphMigrations['cc-key-stage-node'],
},
'cc-key-stage-syllabus-node': {
props: ccGraphShapeProps['cc-key-stage-syllabus-node'],
migrations: ccGraphMigrations['cc-key-stage-syllabus-node'],
},
'cc-year-group-syllabus-node': {
props: ccGraphShapeProps['cc-year-group-syllabus-node'],
migrations: ccGraphMigrations['cc-year-group-syllabus-node'],
},
'cc-subject-node': {
props: ccGraphShapeProps['cc-subject-node'],
migrations: ccGraphMigrations['cc-subject-node'],
},
'cc-topic-node': {
props: ccGraphShapeProps['cc-topic-node'],
migrations: ccGraphMigrations['cc-topic-node'],
},
'cc-topic-lesson-node': {
props: ccGraphShapeProps['cc-topic-lesson-node'],
migrations: ccGraphMigrations['cc-topic-lesson-node'],
},
'cc-learning-statement-node': {
props: ccGraphShapeProps['cc-learning-statement-node'],
migrations: ccGraphMigrations['cc-learning-statement-node'],
},
'cc-science-lab-node': {
props: ccGraphShapeProps['cc-science-lab-node'],
migrations: ccGraphMigrations['cc-science-lab-node'],
},
'cc-teacher-timetable-node': {
props: ccGraphShapeProps['cc-teacher-timetable-node'],
migrations: ccGraphMigrations['cc-teacher-timetable-node'],
},
'cc-timetable-lesson-node': {
props: ccGraphShapeProps['cc-timetable-lesson-node'],
migrations: ccGraphMigrations['cc-timetable-lesson-node'],
},
'cc-planned-lesson-node': {
props: ccGraphShapeProps['cc-planned-lesson-node'],
migrations: ccGraphMigrations['cc-planned-lesson-node'],
},
'cc-school-timetable-node': {
props: ccGraphShapeProps['cc-school-timetable-node'],
migrations: ccGraphMigrations['cc-school-timetable-node'],
},
'cc-academic-year-node': {
props: ccGraphShapeProps['cc-academic-year-node'],
migrations: ccGraphMigrations['cc-academic-year-node'],
},
'cc-academic-term-node': {
props: ccGraphShapeProps['cc-academic-term-node'],
migrations: ccGraphMigrations['cc-academic-term-node'],
},
'cc-academic-week-node': {
props: ccGraphShapeProps['cc-academic-week-node'],
migrations: ccGraphMigrations['cc-academic-week-node'],
},
'cc-academic-day-node': {
props: ccGraphShapeProps['cc-academic-day-node'],
migrations: ccGraphMigrations['cc-academic-day-node'],
},
'cc-academic-period-node': {
props: ccGraphShapeProps['cc-academic-period-node'],
migrations: ccGraphMigrations['cc-academic-period-node'],
},
'cc-registration-period-node': {
props: ccGraphShapeProps['cc-registration-period-node'],
migrations: ccGraphMigrations['cc-registration-period-node'],
},
'cc-user-teacher-timetable-node': {
props: ccGraphShapeProps['cc-user-teacher-timetable-node'],
migrations: ccGraphMigrations['cc-user-teacher-timetable-node'],
},
'cc-user-timetable-lesson-node': {
props: ccGraphShapeProps['cc-user-timetable-lesson-node'],
migrations: ccGraphMigrations['cc-user-timetable-lesson-node'],
},
},
bindings: {
...defaultBindingSchemas,
'cc-slide-layout': {
props: ccBindingProps['cc-slide-layout'],
migrations: ccBindingMigrations['cc-slide-layout'],
},
},
})

199
src/server/server.bun.ts Normal file
View File

@ -0,0 +1,199 @@
// External imports
import { TLSocketRoom } from '@tldraw/sync-core'
import { IRequest, Router, RouterType, cors, json } from 'itty-router'
import { Readable } from 'stream'
import {
createTLStore,
} from 'tldraw'
// Internal imports
import { loadAsset, storeAsset } from './assets'
import { makeOrLoadRoom } from './rooms'
import { unfurl } from './unfurl'
import { server_schema_default } from './schema'
import { logger } from './../logger'
// Add debug logging for environment variables
logger.info('Environment variables:', {
PORT: process.env.PORT,
PORT_TLDRAW_SYNC: process.env.PORT_TLDRAW_SYNC,
NODE_ENV: process.env.NODE_ENV
});
// Be explicit about port precedence
const PORT = process.env.PORT_TLDRAW_SYNC || 5002
// Log the port being used
logger.info(`Using port: ${PORT}`)
const { corsify, preflight } = cors({ origin: '*' })
const router: RouterType<IRequest, any, any> = Router()
.all('*', preflight)
.get(`/connect/:roomId`, async (req) => {
const {roomId} = req.params
const {sessionId} = req.query
logger.info(`Connecting to room: ${roomId}, session: ${sessionId}`)
server.upgrade(req, { data: { roomId, sessionId } })
return new Response(null, { status: 101 })
})
.put(`/uploads/:id`, async (req) => {
const {id} = req.params;
logger.info(`Received upload request for ID: ${id}`);
try {
const buffer = await req.arrayBuffer(); // Directly convert the incoming request body to an ArrayBuffer
const stream = Readable.from(Buffer.from(buffer)); // Convert ArrayBuffer to Node.js Readable Stream
await storeAsset(id, stream);
const response = new Response(JSON.stringify({ ok: true }), {
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*'
},
status: 200
}); // TODO: Unsafe, change
logger.info(`Upload successful for ID: ${id}`);
return response;
} catch (error) {
logger.error(`Error storing asset with ID: ${id}`, error);
return new Response('Internal Server Error', { status: 500 });
}
})
.get(`/uploads/:id`, async (req) => {
const id = (req.params as any).id as string
logger.info(`Received request to load asset with ID: ${id}`)
try {
const asset = await loadAsset(id)
const response = new Response(asset)
response.headers.set('Access-Control-Allow-Origin', '*') // TODO: Unsafe, change
logger.info(`Asset loaded successfully for ID: ${id}`)
return response
} catch (error) {
logger.error(`Error loading asset with ID: ${id}`, error)
return new Response('Internal Server Error', { status: 500 })
}
})
.get(`/unfurl`, async (req) => {
const url = (req.query as any).url as string
logger.info(`Received unfurl request for URL: ${url}`)
try {
const data = await unfurl(url)
const response = json(data)
response.headers.set('Access-Control-Allow-Origin', '*') // TODO: Unsafe, change
logger.info(`Unfurling successful for URL: ${url}`)
return response
} catch (error) {
logger.error(`Error unfurling URL: ${url}`, error)
return new Response('Internal Server Error', { status: 500 })
}
})
.all('*', (req) => {
logger.info(`Received request for unknown route: ${req.url}`);
const response = new Response('Not found', { status: 404 });
response.headers.set('Access-Control-Allow-Origin', '*'); // TODO: Unsafe, change
return response;
})
const server = Bun.serve<{ room?: TLSocketRoom<any, void>; sessionId: string; roomId: string }>({
port: parseInt(PORT as string), // Ensure it's parsed as a number
fetch(req) {
try {
logger.info(`Server started on port: ${PORT}`) // Add explicit port logging
logger.info('Received request: ', req.url)
return router.fetch(req).then(corsify)
} catch (e) {
logger.error('Error handling request: ', e)
return new Response('Something went wrong', {
status: 500,
})
}
},
websocket: {
async open(socket) {
logger.debug(`WebSocket connection attempt for room: ${socket.data.roomId}`, {
sessionId: socket.data.sessionId
});
try {
const { sessionId, roomId } = socket.data;
if (!sessionId || !roomId) {
logger.error('Missing sessionId or roomId in WebSocket connection data', {
sessionId,
roomId
});
socket.close(4000, 'Missing data');
return;
}
logger.info(`WebSocket opened for room: ${roomId}, session: ${sessionId}`);
const room = await makeOrLoadRoom(roomId, server_schema_default);
if (!room) {
logger.error('Failed to create or load room', {
roomId,
sessionId
});
socket.close(4001, 'Failed to load room');
return;
}
room.handleSocketConnect({ sessionId, socket });
socket.data.room = room;
logger.info(`Successfully connected to room: ${roomId}`, {
sessionId,
roomId
});
} catch (error) {
logger.error('Error during WebSocket open:', error);
socket.close(1011, 'Internal error');
}
},
async message(ws, message) {
try {
logger.debug(`WebSocket message for session: ${ws.data.sessionId}`, {
message,
roomId: ws.data.roomId
});
if (!ws.data.room) {
logger.error('No room found for WebSocket message', {
sessionId: ws.data.sessionId,
roomId: ws.data.roomId
});
ws.close(4002, 'No room found');
return;
}
ws.data.room.handleSocketMessage(ws.data.sessionId, message);
} catch (error) {
logger.error('Error handling WebSocket message:', error);
ws.close(1011, 'Message handling error');
}
},
drain(ws) {
logger.info(`WebSocket drain for session: ${ws.data.sessionId}`, {
roomId: ws.data.roomId
});
ws.close();
},
close(ws) {
logger.info(`WebSocket closed for session: ${ws.data.sessionId}`, {
roomId: ws.data.roomId
});
if (ws.data.room) {
ws.data.room.handleSocketClose(ws.data.sessionId);
}
},
},
})
// Add explicit logging of the server configuration
logger.info('Server configuration:', {
port: server.port,
hostname: server.hostname
})
logger.info(`Listening for connections on URL: ${server.url}`)
logger.info(`Listening on localhost:${PORT}`)
logger.info(`Server: ${server}`)

14
src/server/unfurl.ts Normal file
View File

@ -0,0 +1,14 @@
import _unfurl from 'unfurl.js'
export async function unfurl(url: string) {
const { title, description, open_graph, twitter_card, favicon } = await _unfurl.unfurl(url)
const image = open_graph?.images?.[0]?.url || twitter_card?.images?.[0]?.url
return {
title,
description,
image,
favicon,
}
}

View File

@ -0,0 +1,310 @@
export interface BaseNodeInterface {
w: number;
h: number;
color: string;
__primarylabel__: string;
unique_id: string;
path: string;
created: string;
merged: string;
}
// Users
export interface UserNodeInterface extends BaseNodeInterface
{
user_id: string;
user_type: string;
user_name: string;
user_email: string;
worker_node_data: string;
}
export interface DeveloperNodeInterface extends BaseNodeInterface{
user_id: string;
user_type: string;
user_name: string;
user_email: string;
}
export interface TeacherNodeInterface extends BaseNodeInterface{
teacher_code: string;
teacher_name_formal: string;
teacher_email: string;
worker_db_name: string;
}
export interface StudentNodeInterface extends BaseNodeInterface{
student_code: string;
student_name_formal: string;
student_email: string;
worker_db_name: string;
}
export interface StandardUserNodeInterface extends BaseNodeInterface{
user_id: string;
user_type: string;
user_name: string;
user_email: string;
}
export interface SchoolAdminNodeInterface extends BaseNodeInterface {
user_id: string;
user_type: string;
user_name: string;
user_email: string;
}
// Calendar
export interface CalendarNodeInterface extends BaseNodeInterface {
name: string;
start_date: string;
end_date: string;
}
export interface CalendarYearNodeInterface extends BaseNodeInterface {
year: string;
}
export interface CalendarMonthNodeInterface extends BaseNodeInterface {
year: string;
month: string;
month_name: string;
}
export interface CalendarWeekNodeInterface extends BaseNodeInterface {
start_date: string;
week_number: string;
iso_week: string;
}
export interface CalendarDayNodeInterface extends BaseNodeInterface {
date: string;
day_of_week: string;
iso_day: string;
}
export interface CalendarTimeChunkNodeInterface extends BaseNodeInterface {
start_time: string;
end_time: string;
}
// School
export interface SchoolNodeInterface extends BaseNodeInterface
{
school_name: string;
school_website: string;
school_uuid: string;
}
export interface DepartmentNodeInterface extends BaseNodeInterface
{
department_name: string;
}
export interface RoomNodeInterface extends BaseNodeInterface {
room_code: string;
room_name: string;
}
export interface SubjectClassNodeInterface extends BaseNodeInterface
{
subject_class_code: string;
year_group: string;
subject: string;
subject_code: string;
}
// Curriculum
export interface PastoralStructureNodeInterface extends BaseNodeInterface {
// No additional properties
}
export interface YearGroupNodeInterface extends BaseNodeInterface {
year_group: string;
year_group_name: string;
}
export interface CurriculumStructureNodeInterface extends BaseNodeInterface {
}
export interface KeyStageNodeInterface extends BaseNodeInterface {
key_stage_name: string;
key_stage: string;
}
export interface KeyStageSyllabusNodeInterface extends BaseNodeInterface {
ks_syllabus_id: string;
ks_syllabus_name: string;
ks_syllabus_key_stage: string;
ks_syllabus_subject: string;
ks_syllabus_subject_code: string;
}
export interface YearGroupSyllabusNodeInterface extends BaseNodeInterface {
yr_syllabus_id: string;
yr_syllabus_name: string;
yr_syllabus_year_group: string;
yr_syllabus_subject: string;
yr_syllabus_subject_code: string;
}
export interface SubjectNodeInterface extends BaseNodeInterface {
subject_code: string;
subject_name: string;
}
export interface TopicNodeInterface extends BaseNodeInterface {
topic_id: string;
topic_title: string;
total_number_of_lessons_for_topic: string;
topic_type: string;
topic_assessment_type: string;
}
export interface TopicLessonNodeInterface extends BaseNodeInterface {
topic_lesson_id: string;
topic_lesson_title: string;
topic_lesson_type: string;
topic_lesson_length: string;
topic_lesson_suggested_activities: string;
topic_lesson_skills_learned: string;
topic_lesson_weblinks: string;
}
export interface LearningStatementNodeInterface extends BaseNodeInterface {
lesson_learning_statement_id: string;
lesson_learning_statement: string;
lesson_learning_statement_type: string;
}
export interface ScienceLabNodeInterface extends BaseNodeInterface {
science_lab_id: string;
science_lab_title: string;
science_lab_summary: string;
science_lab_requirements: string;
science_lab_procedure: string;
science_lab_safety: string;
science_lab_weblinks: string;
}
// School Timetable
export interface SchoolTimetableNodeInterface extends BaseNodeInterface {
start_date: string;
end_date: string;
}
export interface AcademicYearNodeInterface extends BaseNodeInterface {
year: string;
}
export interface AcademicTermNodeInterface extends BaseNodeInterface {
term_name: string;
term_number: string;
start_date: string;
end_date: string;
}
export interface AcademicTermBreakNodeInterface extends BaseNodeInterface {
term_break_name: string;
start_date: string;
end_date: string;
}
export interface AcademicWeekNodeInterface extends BaseNodeInterface {
academic_week_number: string;
start_date: string;
week_type: string;
}
export interface HolidayWeekNodeInterface extends BaseNodeInterface {
start_date: string;
}
export interface AcademicDayNodeInterface extends BaseNodeInterface {
academic_day: string;
date: string;
day_of_week: string;
day_type: string;
}
export interface OffTimetableDayNodeInterface extends BaseNodeInterface {
date: string;
day_of_week: string;
}
export interface StaffDayNodeInterface extends BaseNodeInterface {
date: string;
day_of_week: string;
}
export interface HolidayDayNodeInterface extends BaseNodeInterface {
date: string;
day_of_week: string;
}
export interface AcademicPeriodNodeInterface extends BaseNodeInterface {
name: string;
date: string;
start_time: string;
end_time: string;
period_code: string;
}
export interface RegistrationPeriodNodeInterface extends BaseNodeInterface {
name: string;
date: string;
start_time: string;
end_time: string;
period_code: string;
}
export interface BreakPeriodNodeInterface extends BaseNodeInterface {
name: string;
date: string;
start_time: string;
end_time: string;
}
export interface OffTimetablePeriodNodeInterface extends BaseNodeInterface {
name: string;
date: string;
start_time: string;
end_time: string;
}
// Teacher timetable
export interface TeacherTimetableNodeInterface extends BaseNodeInterface {
}
export interface TimetableLessonNodeInterface extends BaseNodeInterface {
subject_class: string;
date: string;
start_time: string;
end_time: string;
period_code: string;
}
export interface PlannedLessonNodeInterface extends BaseNodeInterface {
date: string;
start_time: string;
end_time: string;
period_code: string;
subject_class: string;
year_group: string;
subject: string;
teacher_code: string;
planning_status: string;
topic_code?: string | null | undefined;
topic_name?: string | null | undefined;
lesson_code?: string | null | undefined;
lesson_name?: string | null | undefined;
learning_statement_codes?: string | null | undefined;
learning_statements?: string | null | undefined;
learning_resource_codes?: string | null | undefined;
learning_resources?: string | null | undefined;
}

View File

@ -0,0 +1,12 @@
export interface BaseRelationshipInterface {
__relationshiptype__: string;
source: string;
target: string;
}
// General
export interface GeneralRelationshipInterface extends BaseRelationshipInterface {
__relationshiptype__: string;
source: string;
target: string;
}

View File

@ -0,0 +1,32 @@
import { ccGraphShapeProps } from './cc-graph-props'
import { createShapePropsMigrationIds, createShapePropsMigrationSequence } from 'tldraw'
import { CCGraphShape, GraphShapeType } from './cc-graph-types'
// Helper function to create version IDs for a shape type
const createVersions = (shapeType: GraphShapeType) => {
return createShapePropsMigrationIds(shapeType, {
Initial: 1 // All shapes start at version 1 as required by TLDraw
})
}
// Helper function to create a migration sequence for a shape
const createMigrationSequence = (shapeType: GraphShapeType) => {
const versions = createVersions(shapeType)
return createShapePropsMigrationSequence({
sequence: [
{
id: versions.Initial,
up: (props: CCGraphShape['props']) => {
// Initial version - no changes needed
return props
},
},
],
})
}
// Create migrations for all graph shapes
export const ccGraphMigrations = Object.keys(ccGraphShapeProps).reduce((acc, shapeType) => ({
...acc,
[shapeType]: createMigrationSequence(shapeType as GraphShapeType)
}), {} as Record<GraphShapeType, ReturnType<typeof createShapePropsMigrationSequence>>)

View File

@ -0,0 +1,656 @@
import { T, TLBinding, TLShapeId } from 'tldraw'
import { baseShapeProps } from './cc-props'
import { ShapeState } from './cc-graph-types'
// State props validation
const stateProps = T.object({
parentId: T.optional(T.string.nullable()),
isPageChild: T.optional(T.boolean.nullable()),
hasChildren: T.optional(T.boolean.nullable()),
bindings: T.optional(T.arrayOf(T.object({})).nullable())
})
// Base props for all nodes
const graphBaseProps = {
...baseShapeProps,
__primarylabel__: T.string,
unique_id: T.string,
path: T.string,
created: T.string,
merged: T.string,
state: T.optional(stateProps.nullable()),
defaultComponent: T.optional(T.boolean.nullable())
}
// Props for specific node types
export const ccGraphShapeProps = {
'cc-user-node': {
...graphBaseProps,
user_name: T.string,
user_email: T.string,
user_type: T.string,
user_id: T.string,
worker_node_data: T.string,
},
'cc-teacher-node': {
...graphBaseProps,
teacher_code: T.string,
teacher_name_formal: T.string,
teacher_email: T.string,
user_db_name: T.string,
worker_db_name: T.string,
},
'cc-student-node': {
...graphBaseProps,
student_code: T.string,
student_name_formal: T.string,
student_email: T.string,
worker_db_name: T.string,
},
'cc-calendar-node': {
...graphBaseProps,
name: T.string,
calendar_type: T.string,
calendar_name: T.string,
start_date: T.string,
end_date: T.string,
},
'cc-calendar-year-node': {
...graphBaseProps,
year: T.string,
},
'cc-calendar-month-node': {
...graphBaseProps,
year: T.string,
month: T.string,
month_name: T.string,
},
'cc-calendar-week-node': {
...graphBaseProps,
start_date: T.string,
week_number: T.string,
iso_week: T.string,
},
'cc-calendar-day-node': {
...graphBaseProps,
date: T.string,
day_of_week: T.string,
iso_day: T.string,
},
'cc-calendar-time-chunk-node': {
...graphBaseProps,
start_time: T.string,
end_time: T.string,
},
'cc-school-node': {
...graphBaseProps,
school_uuid: T.string,
school_name: T.string,
school_website: T.string,
},
'cc-department-node': {
...graphBaseProps,
department_name: T.string,
},
'cc-room-node': {
...graphBaseProps,
room_code: T.string,
room_name: T.string,
},
'cc-subject-class-node': {
...graphBaseProps,
subject_class_code: T.string,
year_group: T.string,
subject: T.string,
subject_code: T.string,
},
'cc-pastoral-structure-node': {
...graphBaseProps,
},
'cc-year-group-node': {
...graphBaseProps,
year_group: T.string,
year_group_name: T.string,
},
'cc-curriculum-structure-node': {
...graphBaseProps,
},
'cc-key-stage-node': {
...graphBaseProps,
key_stage_name: T.string,
key_stage: T.string,
},
'cc-key-stage-syllabus-node': {
...graphBaseProps,
ks_syllabus_id: T.string,
ks_syllabus_name: T.string,
ks_syllabus_key_stage: T.string,
ks_syllabus_subject: T.string,
ks_syllabus_subject_code: T.string,
},
'cc-year-group-syllabus-node': {
...graphBaseProps,
yr_syllabus_id: T.string,
yr_syllabus_name: T.string,
yr_syllabus_year_group: T.string,
yr_syllabus_subject: T.string,
yr_syllabus_subject_code: T.string,
},
'cc-subject-node': {
...graphBaseProps,
subject_code: T.string,
subject_name: T.string,
},
'cc-topic-node': {
...graphBaseProps,
topic_id: T.string,
topic_title: T.string,
total_number_of_lessons_for_topic: T.string,
topic_type: T.string,
topic_assessment_type: T.string,
},
'cc-topic-lesson-node': {
...graphBaseProps,
topic_lesson_id: T.string,
topic_lesson_title: T.string,
topic_lesson_type: T.string,
topic_lesson_length: T.string,
topic_lesson_skills_learned: T.string,
topic_lesson_suggested_activities: T.string,
topic_lesson_weblinks: T.string,
},
'cc-learning-statement-node': {
...graphBaseProps,
lesson_learning_statement_id: T.string,
lesson_learning_statement: T.string,
lesson_learning_statement_type: T.string,
},
'cc-science-lab-node': {
...graphBaseProps,
science_lab_id: T.string,
science_lab_title: T.string,
science_lab_summary: T.string,
science_lab_requirements: T.string,
science_lab_procedure: T.string,
science_lab_safety: T.string,
science_lab_weblinks: T.string,
},
'cc-teacher-timetable-node': {
...graphBaseProps,
teacher_id: T.string,
start_date: T.string,
end_date: T.string,
},
'cc-timetable-lesson-node': {
...graphBaseProps,
subject_class: T.string,
date: T.string,
start_time: T.string,
end_time: T.string,
period_code: T.string,
},
'cc-planned-lesson-node': {
...graphBaseProps,
date: T.string,
start_time: T.string,
end_time: T.string,
period_code: T.string,
subject_class: T.string,
year_group: T.string,
subject: T.string,
teacher_code: T.string,
planning_status: T.string,
topic_code: T.string,
topic_name: T.string,
lesson_code: T.string,
lesson_name: T.string,
learning_statement_codes: T.string,
learning_statements: T.string,
learning_resource_codes: T.string,
learning_resources: T.string,
},
'cc-school-timetable-node': {
...graphBaseProps,
start_date: T.string,
end_date: T.string,
},
'cc-academic-year-node': {
...graphBaseProps,
year: T.string,
},
'cc-academic-term-node': {
...graphBaseProps,
term_name: T.string,
term_number: T.string,
start_date: T.string,
end_date: T.string,
},
'cc-academic-week-node': {
...graphBaseProps,
academic_week_number: T.string,
start_date: T.string,
week_type: T.string,
},
'cc-academic-day-node': {
...graphBaseProps,
academic_day: T.string,
date: T.string,
day_of_week: T.string,
day_type: T.string,
},
'cc-academic-period-node': {
...graphBaseProps,
name: T.string,
date: T.string,
start_time: T.string,
end_time: T.string,
period_code: T.string,
},
'cc-registration-period-node': {
...graphBaseProps,
name: T.string,
date: T.string,
start_time: T.string,
end_time: T.string,
period_code: T.string,
},
'cc-department-structure-node': {
...graphBaseProps,
department_structure_type: T.string,
},
'cc-user-teacher-timetable-node': {
...graphBaseProps,
school_db_name: T.string,
school_timetable_id: T.string,
},
'cc-user-timetable-lesson-node': {
...graphBaseProps,
subject_class: T.string,
date: T.string,
start_time: T.string,
end_time: T.string,
period_code: T.string,
school_db_name: T.string,
school_period_id: T.string,
},
} as const
// Default props getters
export const getDefaultBaseProps = () => ({
w: 200 as number,
h: 200 as number,
headerColor: '#3e6589' as string,
backgroundColor: '#f0f0f0' as string,
title: 'Untitled' as string,
isLocked: false as boolean,
unique_id: '' as string,
path: '' as string,
created: '' as string,
merged: '' as string,
state: {
parentId: null as TLShapeId | null,
isPageChild: true as boolean | null,
hasChildren: null as boolean | null,
bindings: null as TLBinding[] | null
} as ShapeState | null,
defaultComponent: true as boolean | null
})
export const getDefaultCCUserNodeProps = () => ({
...getDefaultBaseProps(),
__primarylabel__: 'User',
user_name: '',
user_email: '',
user_type: '',
user_id: '',
worker_node_data: ''
})
export const getDefaultCCTeacherNodeProps = () => ({
...getDefaultBaseProps(),
__primarylabel__: 'Teacher',
teacher_code: '',
teacher_name_formal: '',
teacher_email: '',
user_db_name: '',
worker_db_name: '',
})
export const getDefaultCCStudentNodeProps = () => ({
...getDefaultBaseProps(),
__primarylabel__: 'Student',
student_code: '',
student_name_formal: '',
student_email: '',
worker_db_name: '',
})
export const getDefaultCCCalendarNodeProps = () => ({
...getDefaultBaseProps(),
title: 'Calendar',
__primarylabel__: 'Calendar',
name: '',
calendar_type: '',
calendar_name: '',
start_date: '',
end_date: '',
})
export const getDefaultCCCalendarYearNodeProps = () => ({
...getDefaultBaseProps(),
title: 'Calendar Year',
__primarylabel__: 'Calendar Year',
year: '',
})
export const getDefaultCCCalendarMonthNodeProps = () => ({
...getDefaultBaseProps(),
title: 'Calendar Month',
__primarylabel__: 'Calendar Month',
year: '',
month: '',
month_name: '',
})
export const getDefaultCCCalendarWeekNodeProps = () => ({
...getDefaultBaseProps(),
title: 'Calendar Week',
__primarylabel__: 'Calendar Week',
start_date: '',
week_number: '',
iso_week: '',
})
export const getDefaultCCCalendarDayNodeProps = () => ({
...getDefaultBaseProps(),
title: 'Calendar Day',
__primarylabel__: 'Calendar Day',
date: '',
day_of_week: '',
iso_day: '',
})
export const getDefaultCCCalendarTimeChunkNodeProps = () => ({
...getDefaultBaseProps(),
title: 'Calendar Time Chunk',
__primarylabel__: 'Calendar Time Chunk',
start_time: '',
end_time: '',
})
export const getDefaultCCSchoolNodeProps = () => ({
...getDefaultBaseProps(),
title: 'School',
__primarylabel__: 'School',
school_uuid: '',
school_name: '',
school_website: '',
})
export const getDefaultCCDepartmentNodeProps = () => ({
...getDefaultBaseProps(),
title: 'Department',
__primarylabel__: 'Department',
department_name: '',
})
export const getDefaultCCRoomNodeProps = () => ({
...getDefaultBaseProps(),
title: 'Room',
__primarylabel__: 'Room',
room_code: '',
room_name: '',
})
export const getDefaultCCSubjectClassNodeProps = () => ({
...getDefaultBaseProps(),
title: 'Subject Class',
__primarylabel__: 'Subject Class',
subject_class_code: '',
year_group: '',
subject: '',
subject_code: '',
})
export const getDefaultCCPastoralStructureNodeProps = () => ({
...getDefaultBaseProps(),
title: 'Pastoral Structure',
__primarylabel__: 'Pastoral Structure',
pastoral_structure_type: '',
})
export const getDefaultCCYearGroupNodeProps = () => ({
...getDefaultBaseProps(),
title: 'Year Group',
__primarylabel__: 'Year Group',
year_group: '',
year_group_name: '',
})
export const getDefaultCCCurriculumStructureNodeProps = () => ({
...getDefaultBaseProps(),
title: 'Curriculum Structure',
__primarylabel__: 'Curriculum Structure',
curriculum_structure_type: '',
})
export const getDefaultCCKeyStageNodeProps = () => ({
...getDefaultBaseProps(),
title: 'Key Stage',
__primarylabel__: 'Key Stage',
key_stage_name: '',
key_stage: '',
})
export const getDefaultCCKeyStageSyllabusNodeProps = () => ({
...getDefaultBaseProps(),
title: 'Key Stage Syllabus',
__primarylabel__: 'Key Stage Syllabus',
ks_syllabus_id: '',
ks_syllabus_name: '',
ks_syllabus_key_stage: '',
ks_syllabus_subject: '',
ks_syllabus_subject_code: '',
})
export const getDefaultCCYearGroupSyllabusNodeProps = () => ({
...getDefaultBaseProps(),
title: 'Year Group Syllabus',
__primarylabel__: 'Year Group Syllabus',
yr_syllabus_id: '',
yr_syllabus_name: '',
yr_syllabus_year_group: '',
yr_syllabus_subject: '',
yr_syllabus_subject_code: '',
})
export const getDefaultCCSubjectNodeProps = () => ({
...getDefaultBaseProps(),
title: 'Subject',
__primarylabel__: 'Subject',
subject_code: '',
subject_name: '',
})
export const getDefaultCCTopicNodeProps = () => ({
...getDefaultBaseProps(),
title: 'Topic',
__primarylabel__: 'Topic',
topic_id: '',
topic_title: '',
total_number_of_lessons_for_topic: '',
topic_type: '',
topic_assessment_type: '',
})
export const getDefaultCCTopicLessonNodeProps = () => ({
...getDefaultBaseProps(),
title: 'Topic Lesson',
__primarylabel__: 'Topic Lesson',
topic_lesson_id: '',
topic_lesson_title: '',
topic_lesson_type: '',
topic_lesson_length: '',
topic_lesson_skills_learned: '',
topic_lesson_suggested_activities: '',
topic_lesson_weblinks: '',
})
export const getDefaultCCLearningStatementNodeProps = () => ({
...getDefaultBaseProps(),
title: 'Learning Statement',
__primarylabel__: 'Learning Statement',
lesson_learning_statement_id: '',
lesson_learning_statement: '',
lesson_learning_statement_type: '',
})
export const getDefaultCCScienceLabNodeProps = () => ({
...getDefaultBaseProps(),
title: 'Science Lab',
__primarylabel__: 'Science Lab',
science_lab_id: '',
science_lab_title: '',
science_lab_summary: '',
science_lab_requirements: '',
science_lab_procedure: '',
science_lab_safety: '',
science_lab_weblinks: '',
})
export const getDefaultCCTeacherTimetableNodeProps = () => ({
...getDefaultBaseProps(),
title: 'Teacher Timetable',
__primarylabel__: 'Teacher Timetable',
teacher_id: '',
start_date: '',
end_date: '',
})
export const getDefaultCCTimetableLessonNodeProps = () => ({
...getDefaultBaseProps(),
title: 'Timetable Lesson',
__primarylabel__: 'Timetable Lesson',
subject_class: '',
date: '',
start_time: '',
end_time: '',
period_code: '',
})
export const getDefaultCCPlannedLessonNodeProps = () => ({
...getDefaultBaseProps(),
title: 'Planned Lesson',
__primarylabel__: 'Planned Lesson',
date: '',
start_time: '',
end_time: '',
period_code: '',
subject_class: '',
year_group: '',
subject: '',
teacher_code: '',
planning_status: '',
topic_code: '',
topic_name: '',
lesson_code: '',
lesson_name: '',
learning_statement_codes: '',
learning_statements: '',
learning_resource_codes: '',
learning_resources: '',
})
export const getDefaultCCSchoolTimetableNodeProps = () => ({
...getDefaultBaseProps(),
title: 'School Timetable',
__primarylabel__: 'School Timetable',
start_date: '',
end_date: '',
})
export const getDefaultCCAcademicYearNodeProps = () => ({
...getDefaultBaseProps(),
title: 'Academic Year',
__primarylabel__: 'Academic Year',
year: '',
})
export const getDefaultCCAcademicTermNodeProps = () => ({
...getDefaultBaseProps(),
title: 'Academic Term',
__primarylabel__: 'Academic Term',
term_name: '',
term_number: '',
start_date: '',
end_date: '',
})
export const getDefaultCCAcademicWeekNodeProps = () => ({
...getDefaultBaseProps(),
title: 'Academic Week',
__primarylabel__: 'Academic Week',
academic_week_number: '',
start_date: '',
week_type: '',
})
export const getDefaultCCAcademicDayNodeProps = () => ({
...getDefaultBaseProps(),
title: 'Academic Day',
__primarylabel__: 'Academic Day',
academic_day: '',
date: '',
day_of_week: '',
day_type: '',
})
export const getDefaultCCAcademicPeriodNodeProps = () => ({
...getDefaultBaseProps(),
title: 'Academic Period',
__primarylabel__: 'Academic Period',
name: '',
date: '',
start_time: '',
end_time: '',
period_code: '',
})
export const getDefaultCCRegistrationPeriodNodeProps = () => ({
...getDefaultBaseProps(),
title: 'Registration Period',
__primarylabel__: 'Registration Period',
name: '',
date: '',
start_time: '',
end_time: '',
period_code: '',
})
export const getDefaultCCDepartmentStructureNodeProps = () => ({
...getDefaultBaseProps(),
title: 'Department Structure',
__primarylabel__: 'DepartmentStructure',
department_structure_type: '',
})
export const getDefaultCCUserTeacherTimetableNodeProps = () => ({
...getDefaultBaseProps(),
title: 'User Teacher Timetable',
__primarylabel__: 'UserTeacherTimetable',
school_db_name: '',
school_timetable_id: '',
})
export const getDefaultCCUserTimetableLessonNodeProps = () => ({
...getDefaultBaseProps(),
title: 'User Timetable Lesson',
__primarylabel__: 'UserTimetableLesson',
subject_class: '',
date: '',
start_time: '',
end_time: '',
period_code: '',
school_db_name: '',
school_period_id: '',
})

View File

@ -0,0 +1,169 @@
// Shared styles for all nodes
export const SHARED_NODE_STYLES = {
container: {
display: 'flex',
flexDirection: 'column' as const,
padding: '8px',
gap: '4px',
backgroundColor: 'var(--color-muted)',
color: 'var(--color-text)',
borderRadius: '4px',
minWidth: '150px',
},
header: {
fontSize: '14px',
fontWeight: 'bold' as const,
marginBottom: '4px',
color: 'var(--color-text)',
},
property: {
label: {
fontSize: '12px',
color: 'var(--color-text-2)',
marginRight: '4px',
fontWeight: '500' as const,
},
value: {
fontSize: '12px',
color: 'var(--color-text)',
fontWeight: '200' as const,
},
wrapper: {
display: 'flex',
alignItems: 'center',
gap: '4px',
},
},
error: {
container: {
padding: '8px',
backgroundColor: 'var(--color-error)',
color: 'white',
borderRadius: '4px',
fontSize: '12px',
},
message: {
fontWeight: 'bold' as const,
},
details: {
marginTop: '4px',
opacity: 0.8,
}
},
defaultComponent: {
container: {
display: 'flex',
gap: '8px',
marginBottom: '8px',
},
button: {
padding: '4px 8px',
fontSize: '12px',
borderRadius: '4px',
backgroundColor: 'var(--color-muted-2)',
color: 'var(--color-text)',
border: 'none',
cursor: 'pointer',
'&:hover': {
backgroundColor: 'var(--color-muted-3)',
}
}
}
} as const
// Color themes for different node types
export const NODE_THEMES = {
calendar: {
headerColor: '#0066cc',
backgroundColor: '#e6f0ff',
},
academic: {
headerColor: '#008000',
backgroundColor: '#e6ffe6',
},
curriculum: {
headerColor: '#ff8c00',
backgroundColor: '#fff3e6',
},
pastoral: {
headerColor: '#8a2be2',
backgroundColor: '#f5e6ff',
},
people: {
headerColor: '#cc0000',
backgroundColor: '#ffe6e6',
},
resource: {
headerColor: '#cccc00',
backgroundColor: '#fffff0',
},
} as const
// Node type to theme mapping
export const NODE_TYPE_THEMES: Record<string, keyof typeof NODE_THEMES> = {
// Calendar nodes
'cc-calendar-node': 'calendar',
'cc-calendar-year-node': 'calendar',
'cc-calendar-month-node': 'calendar',
'cc-calendar-week-node': 'calendar',
'cc-calendar-day-node': 'calendar',
'cc-calendar-time-chunk-node': 'calendar',
// Academic nodes
'cc-academic-year-node': 'academic',
'cc-academic-term-node': 'academic',
'cc-academic-week-node': 'academic',
'cc-academic-day-node': 'academic',
'cc-academic-period-node': 'academic',
'cc-registration-period-node': 'academic',
'cc-timetable-lesson-node': 'academic',
'cc-planned-lesson-node': 'academic',
'cc-school-timetable-node': 'academic',
'cc-user-teacher-timetable-node': 'academic',
'cc-user-timetable-lesson-node': 'academic',
// Curriculum nodes
'cc-curriculum-structure-node': 'curriculum',
'cc-key-stage-node': 'curriculum',
'cc-key-stage-syllabus-node': 'curriculum',
'cc-year-group-syllabus-node': 'curriculum',
'cc-subject-node': 'curriculum',
'cc-topic-node': 'curriculum',
'cc-topic-lesson-node': 'curriculum',
'cc-learning-statement-node': 'curriculum',
'cc-science-lab-node': 'curriculum',
// Pastoral nodes
'cc-pastoral-structure-node': 'pastoral',
'cc-year-group-node': 'pastoral',
// People nodes
'cc-user-node': 'people',
'cc-teacher-node': 'people',
'cc-student-node': 'people',
// Resource nodes
'cc-school-node': 'resource',
'cc-department-node': 'resource',
'cc-room-node': 'resource',
'cc-subject-class-node': 'resource',
} as const
// Helper function to get theme for a node type
export const getNodeTheme = (nodeType: string) => {
const themeKey = NODE_TYPE_THEMES[nodeType]
return themeKey ? NODE_THEMES[themeKey] : NODE_THEMES.resource // Default to resource theme
}
// Helper function to get styles for a specific node type
export const getNodeStyles = (nodeType: string) => {
const theme = getNodeTheme(nodeType)
return {
...SHARED_NODE_STYLES,
container: {
...SHARED_NODE_STYLES.container,
backgroundColor: theme.backgroundColor,
},
}
}

View File

@ -0,0 +1,387 @@
import { TLBinding, TLBaseShape, TLShapeId } from 'tldraw'
import { CCBaseShape } from './cc-types'
import { CCBaseProps } from './cc-props'
import { ccGraphShapeProps } from './cc-graph-props'
// Export type for graph shape types
export type GraphShapeType = keyof typeof ccGraphShapeProps
export interface ShapeState {
parentId: TLShapeId | null
isPageChild: boolean | null
hasChildren: boolean | null
bindings: TLBinding[] | null
}
export type CCGraphShapeProps = CCBaseProps & {
__primarylabel__: string
unique_id: string
path: string
created: string
merged: string
state: ShapeState | null | undefined
defaultComponent: boolean | null
}
// Define the base shape type for graph shapes
export type CCGraphShape = CCBaseShape & TLBaseShape<GraphShapeType, {
__primarylabel__: CCGraphShapeProps['__primarylabel__']
unique_id: CCGraphShapeProps['unique_id']
path: CCGraphShapeProps['path']
created: CCGraphShapeProps['created']
merged: CCGraphShapeProps['merged']
state: CCGraphShapeProps['state']
defaultComponent: CCGraphShapeProps['defaultComponent']
}>
export type CCUserNodeProps = CCGraphShapeProps & {
user_name: string
user_email: string
user_type: string
user_id: string
worker_node_data: string
}
export type CCTeacherNodeProps = CCGraphShapeProps & {
teacher_code: string
teacher_name_formal: string
teacher_email: string
user_db_name: string
worker_db_name: string
}
export type CCStudentNodeProps = CCGraphShapeProps & {
student_name_formal: string
student_code: string
student_email: string
}
export type CCCalendarNodeProps = CCGraphShapeProps & {
title: string
name: string
calendar_type: string
calendar_name: string
start_date: string
end_date: string
}
export type CCCalendarYearNodeProps = CCGraphShapeProps & {
year: string
}
export type CCCalendarMonthNodeProps = CCGraphShapeProps & {
year: string
month: string
month_name: string
}
export type CCCalendarWeekNodeProps = CCGraphShapeProps & {
start_date: string
week_number: string
iso_week: string
}
export type CCCalendarDayNodeProps = CCGraphShapeProps & {
date: string
day_of_week: string
iso_day: string
}
export type CCCalendarTimeChunkNodeProps = CCGraphShapeProps & {
start_time: string
end_time: string
}
export type CCSchoolNodeProps = CCGraphShapeProps & {
school_uuid: string
school_name: string
school_website: string
}
export type CCDepartmentNodeProps = CCGraphShapeProps & {
department_name: string
}
export type CCRoomNodeProps = CCGraphShapeProps & {
room_code: string
room_name: string
}
export type CCSubjectClassNodeProps = CCGraphShapeProps & {
subject_class_code: string
year_group: string
subject: string
subject_code: string
}
export type CCPastoralStructureNodeProps = CCGraphShapeProps & {
pastoral_structure_type: string
}
export type CCYearGroupNodeProps = CCGraphShapeProps & {
year_group: string
year_group_name: string
}
export type CCCurriculumStructureNodeProps = CCGraphShapeProps & {
curriculum_structure_type: string
}
export type CCKeyStageNodeProps = CCGraphShapeProps & {
key_stage_name: string
key_stage: string
}
export type CCKeyStageSyllabusNodeProps = CCGraphShapeProps & {
ks_syllabus_id: string
ks_syllabus_name: string
ks_syllabus_key_stage: string
ks_syllabus_subject: string
ks_syllabus_subject_code: string
}
export type CCYearGroupSyllabusNodeProps = CCGraphShapeProps & {
yr_syllabus_id: string
yr_syllabus_name: string
yr_syllabus_year_group: string
yr_syllabus_subject: string
yr_syllabus_subject_code: string
}
export type CCSubjectNodeProps = CCGraphShapeProps & {
subject_code: string
subject_name: string
}
export type CCTopicNodeProps = CCGraphShapeProps & {
topic_id: string
topic_title: string
total_number_of_lessons_for_topic: string
topic_type: string
topic_assessment_type: string
}
export type CCTopicLessonNodeProps = CCGraphShapeProps & {
topic_lesson_id: string
topic_lesson_title: string
topic_lesson_type: string
topic_lesson_length: string
topic_lesson_skills_learned: string
topic_lesson_suggested_activities: string
topic_lesson_weblinks: string
}
export type CCLearningStatementNodeProps = CCGraphShapeProps & {
lesson_learning_statement_id: string
lesson_learning_statement: string
lesson_learning_statement_type: string
}
export type CCScienceLabNodeProps = CCGraphShapeProps & {
science_lab_id: string
science_lab_title: string
science_lab_summary: string
science_lab_requirements: string
science_lab_procedure: string
science_lab_safety: string
science_lab_weblinks: string
}
export type CCTeacherTimetableNodeProps = CCGraphShapeProps & {
teacher_id: string
start_date: string
end_date: string
}
export type CCTimetableLessonNodeProps = CCGraphShapeProps & {
subject_class: string
date: string
start_time: string
end_time: string
period_code: string
}
export type CCPlannedLessonNodeProps = CCGraphShapeProps & {
date: string
start_time: string
end_time: string
period_code: string
subject_class: string
year_group: string
subject: string
teacher_code: string
planning_status: string
topic_code: string
topic_name: string
lesson_code: string
lesson_name: string
learning_statement_codes: string
learning_statements: string
learning_resource_codes: string
learning_resources: string
}
export type CCSchoolTimetableNodeProps = CCGraphShapeProps & {
start_date: string
end_date: string
}
export type CCAcademicYearNodeProps = CCGraphShapeProps & {
year: string
}
export type CCAcademicTermNodeProps = CCGraphShapeProps & {
term_name: string
term_number: string
start_date: string
end_date: string
}
export type CCAcademicWeekNodeProps = CCGraphShapeProps & {
academic_week_number: string
start_date: string
week_type: string
}
export type CCAcademicDayNodeProps = CCGraphShapeProps & {
academic_day: string
date: string
day_of_week: string
day_type: string
}
export type CCAcademicPeriodNodeProps = CCGraphShapeProps & {
name: string
date: string
start_time: string
end_time: string
period_code: string
}
export type CCRegistrationPeriodNodeProps = CCGraphShapeProps & {
name: string
date: string
start_time: string
end_time: string
period_code: string
}
export type CCDepartmentStructureNodeProps = CCGraphShapeProps & {
department_structure_type: string
}
export type CCUserTeacherTimetableNodeProps = CCGraphShapeProps & {
school_db_name: string
school_timetable_id: string
}
export type CCUserTimetableLessonNodeProps = CCGraphShapeProps & {
subject_class: string
date: string
start_time: string
end_time: string
period_code: string
school_db_name: string
school_period_id: string
}
// Define a type-safe mapping of node types to their configurations
export type CCNodeTypes = {
User: { props: CCUserNodeProps }
Developer: { props: CCUserNodeProps }
Teacher: { props: CCTeacherNodeProps }
Student: { props: CCStudentNodeProps }
Calendar: { props: CCCalendarNodeProps }
TeacherTimetable: { props: CCTeacherTimetableNodeProps }
TimetableLesson: { props: CCTimetableLessonNodeProps }
PlannedLesson: { props: CCPlannedLessonNodeProps }
School: { props: CCSchoolNodeProps }
CalendarYear: { props: CCCalendarYearNodeProps }
CalendarMonth: { props: CCCalendarMonthNodeProps }
CalendarWeek: { props: CCCalendarWeekNodeProps }
CalendarDay: { props: CCCalendarDayNodeProps }
CalendarTimeChunk: { props: CCCalendarTimeChunkNodeProps }
ScienceLab: { props: CCScienceLabNodeProps }
KeyStageSyllabus: { props: CCKeyStageSyllabusNodeProps }
YearGroupSyllabus: { props: CCYearGroupSyllabusNodeProps }
CurriculumStructure: { props: CCCurriculumStructureNodeProps }
Topic: { props: CCTopicNodeProps }
TopicLesson: { props: CCTopicLessonNodeProps }
LearningStatement: { props: CCLearningStatementNodeProps }
SchoolTimetable: { props: CCSchoolTimetableNodeProps }
AcademicYear: { props: CCAcademicYearNodeProps }
AcademicTerm: { props: CCAcademicTermNodeProps }
AcademicWeek: { props: CCAcademicWeekNodeProps }
AcademicDay: { props: CCAcademicDayNodeProps }
AcademicPeriod: { props: CCAcademicPeriodNodeProps }
RegistrationPeriod: { props: CCRegistrationPeriodNodeProps }
PastoralStructure: { props: CCPastoralStructureNodeProps }
KeyStage: { props: CCKeyStageNodeProps }
Department: { props: CCDepartmentNodeProps }
Room: { props: CCRoomNodeProps }
SubjectClass: { props: CCSubjectClassNodeProps }
DepartmentStructure: { props: CCDepartmentStructureNodeProps }
UserTeacherTimetable: { props: CCUserTeacherTimetableNodeProps }
UserTimetableLesson: { props: CCUserTimetableLessonNodeProps }
}
// Helper function to get shape type from node type
export const getShapeType = (nodeType: keyof CCNodeTypes): string => {
return `cc-${nodeType.replace(/([A-Z])/g, '-$1').toLowerCase().substring(1)}-node`;
}
// Helper function to get allowed props from node type
export const getAllowedProps = (): string[] => {
return ['__primarylabel__', 'unique_id'];
}
// Helper function to get node configuration
export const getNodeConfig = <T extends keyof CCNodeTypes>(nodeType: T) => {
const shapeType = getShapeType(nodeType);
return {
shapeType,
allowedProps: getAllowedProps()
};
}
// Helper function to check if a string is a valid node type
export const isValidNodeType = (type: string): type is keyof CCNodeTypes => {
return type in {
User: true,
Developer: true,
Teacher: true,
Student: true,
Calendar: true,
TeacherTimetable: true,
TimetableLesson: true,
PlannedLesson: true,
School: true,
CalendarYear: true,
CalendarMonth: true,
CalendarWeek: true,
CalendarDay: true,
CalendarTimeChunk: true,
ScienceLab: true,
KeyStageSyllabus: true,
YearGroupSyllabus: true,
CurriculumStructure: true,
Topic: true,
TopicLesson: true,
LearningStatement: true,
SchoolTimetable: true,
AcademicYear: true,
AcademicTerm: true,
AcademicWeek: true,
AcademicDay: true,
AcademicPeriod: true,
RegistrationPeriod: true,
PastoralStructure: true,
KeyStage: true,
Department: true,
Room: true,
SubjectClass: true,
DepartmentStructure: true,
UserTeacherTimetable: true,
UserTimetableLesson: true,
};
}

View File

@ -0,0 +1,246 @@
import { TLRecord, TLShape } from 'tldraw'
import { getDefaultCCBaseProps, getDefaultCCCalendarProps, getDefaultCCLiveTranscriptionProps, getDefaultCCSettingsProps, getDefaultCCSlideProps, getDefaultCCSlideShowProps, getDefaultCCSlideLayoutBindingProps, getDefaultCCYoutubeEmbedProps, getDefaultCCSearchProps, getDefaultCCWebBrowserProps } from './cc-props'
// Export both shape and binding migrations
export const ccBindingMigrations = {
'cc-slide-layout': {
firstVersion: 1,
currentVersion: 1,
migrators: {
1: {
up: (record: TLRecord) => {
if (record.typeName !== 'binding') return record
if (record.type !== 'cc-slide-layout') return record
return {
...record,
props: {
...getDefaultCCSlideLayoutBindingProps(),
...record.props,
},
}
},
down: (record: TLRecord) => {
return record
},
},
},
},
}
export const ccShapeMigrations = {
base: {
firstVersion: 1,
currentVersion: 1,
migrators: {
1: {
up: (record: TLRecord) => {
if (record.typeName !== 'shape') return record
const shape = record as TLShape
if (shape.type !== 'cc-base') return record
return {
...shape,
props: {
...getDefaultCCBaseProps(),
...shape.props,
},
}
},
down: (record: TLRecord) => {
return record
},
},
},
},
calendar: {
firstVersion: 1,
currentVersion: 1,
migrators: {
1: {
up: (record: TLRecord) => {
if (record.typeName !== 'shape') return record
const shape = record as TLShape
if (shape.type !== 'cc-calendar') return record
return {
...shape,
props: {
...getDefaultCCCalendarProps(),
...shape.props,
},
}
},
down: (record: TLRecord) => {
return record
},
},
},
},
liveTranscription: {
firstVersion: 1,
currentVersion: 1,
migrators: {
1: {
up: (record: TLRecord) => {
if (record.typeName !== 'shape') return record
const shape = record as TLShape
if (shape.type !== 'cc-live-transcription') return record
return {
...shape,
props: {
...getDefaultCCLiveTranscriptionProps(),
...shape.props,
},
}
},
down: (record: TLRecord) => {
return record
},
},
},
},
settings: {
firstVersion: 1,
currentVersion: 1,
migrators: {
1: {
up: (record: TLRecord) => {
if (record.typeName !== 'shape') return record
const shape = record as TLShape
if (shape.type !== 'cc-settings') return record
return {
...shape,
props: {
...getDefaultCCSettingsProps(),
...shape.props,
},
}
},
down: (record: TLRecord) => {
return record
},
},
},
},
slideshow: {
firstVersion: 1,
currentVersion: 1,
migrators: {
1: {
up: (record: TLRecord) => {
if (record.typeName !== 'shape') return record
const shape = record as TLShape
if (shape.type !== 'cc-slideshow') return record
return {
...shape,
props: {
...getDefaultCCSlideShowProps(),
...shape.props,
},
}
},
down: (record: TLRecord) => {
return record
},
},
},
},
slide: {
firstVersion: 1,
currentVersion: 1,
migrators: {
1: {
up: (record: TLRecord) => {
if (record.typeName !== 'shape') return record
const shape = record as TLShape
if (shape.type !== 'cc-slide') return record
return {
...shape,
props: {
...getDefaultCCSlideProps(),
...shape.props,
},
}
},
down: (record: TLRecord) => {
return record
},
},
},
},
'cc-youtube-embed': {
firstVersion: 1,
currentVersion: 1,
migrators: {
1: {
up: (record: TLRecord) => {
if (record.typeName !== 'shape') return record
const shape = record as TLShape
if (shape.type !== 'cc-youtube-embed') return record
return {
...shape,
props: {
...getDefaultCCYoutubeEmbedProps(),
...shape.props,
},
}
},
down: (record: TLRecord) => {
return record
},
},
},
},
search: {
firstVersion: 1,
currentVersion: 1,
migrators: {
1: {
up: (record: TLRecord) => {
if (record.typeName !== 'shape') return record
const shape = record as TLShape
if (shape.type !== 'cc-search') return record
return {
...shape,
props: {
...getDefaultCCSearchProps(),
...shape.props,
},
}
},
down: (record: TLRecord) => {
return record
},
},
},
},
webBrowser: {
firstVersion: 1,
currentVersion: 1,
migrators: {
1: {
up: (record: TLRecord) => {
if (record.typeName !== 'shape') return record
const shape = record as TLShape
if (shape.type !== 'cc-web-browser') return record
return {
...shape,
props: {
...getDefaultCCWebBrowserProps(),
...shape.props,
},
}
},
down: (record: TLRecord) => {
return record
},
},
},
},
}

View File

@ -0,0 +1,262 @@
import { T } from 'tldraw'
import { CC_BASE_STYLE_CONSTANTS, CC_SLIDESHOW_STYLE_CONSTANTS } from './cc-styles'
export interface CCBaseProps {
title: string
w: number
h: number
headerColor: string
backgroundColor: string
isLocked: boolean
}
// Create a constant for the base props validation
export const baseShapeProps = {
title: T.string,
w: T.number,
h: T.number,
headerColor: T.string,
backgroundColor: T.string,
isLocked: T.boolean,
}
export const ccShapeProps = {
base: baseShapeProps,
calendar: {
...baseShapeProps,
date: T.string,
selectedDate: T.string,
view: T.string,
events: T.arrayOf(T.object({
id: T.string,
title: T.string,
start: T.string,
end: T.string,
groupId: T.string.optional(),
extendedProps: T.object({
subjectClass: T.string,
color: T.string,
periodCode: T.string,
path: T.string.optional()
})
})),
},
liveTranscription: {
...baseShapeProps,
isRecording: T.boolean,
segments: T.arrayOf(T.object({
id: T.string,
text: T.string,
completed: T.boolean,
start: T.string,
end: T.string,
})),
currentSegment: T.object({
id: T.string,
text: T.string,
completed: T.boolean,
start: T.string,
end: T.string,
}).optional(),
lastProcessedSegment: T.string.optional(),
},
settings: {
...baseShapeProps,
userEmail: T.string,
userRole: T.string,
isTeacher: T.boolean,
},
slideshow: {
...baseShapeProps,
currentSlideIndex: T.number,
slidePattern: T.string,
numSlides: T.number,
slides: T.arrayOf(T.object({
imageData: T.string,
meta: T.object({
text: T.string,
format: T.string,
}),
})).optional(),
},
slide: {
...baseShapeProps,
imageData: T.string,
meta: T.object({
text: T.string,
format: T.string,
}),
},
'cc-youtube-embed': {
...baseShapeProps,
video_url: T.string,
transcript: T.arrayOf(T.object({
start: T.number,
duration: T.number,
text: T.string,
})),
transcriptVisible: T.boolean,
},
search: {
...baseShapeProps,
query: T.string,
results: T.arrayOf(T.object({
title: T.string,
url: T.string,
content: T.string,
})),
isSearching: T.boolean,
},
webBrowser: {
...baseShapeProps,
url: T.string,
history: T.arrayOf(T.string),
currentHistoryIndex: T.number,
isLoading: T.boolean,
},
}
export const ccBindingProps = {
'cc-slide-layout': {
isMovingWithParent: T.boolean.optional(),
placeholder: T.boolean.optional(),
index: T.string
},
}
export const getDefaultCCBaseProps = () => ({
title: 'Base Shape',
w: 100,
h: 100,
headerColor: '#3e6589',
backgroundColor: '#ffffff',
isLocked: false,
})
export const getDefaultCCCalendarProps = () => ({
...getDefaultCCBaseProps(),
date: new Date().toISOString(),
selectedDate: new Date().toISOString(),
view: 'timeGridWeek',
events: [],
})
export const getDefaultCCLiveTranscriptionProps = () => ({
...getDefaultCCBaseProps(),
isRecording: false,
segments: [],
currentSegment: undefined,
lastProcessedSegment: undefined,
})
export const getDefaultCCSettingsProps = () => ({
...getDefaultCCBaseProps(),
userEmail: '',
userRole: '',
isTeacher: false,
})
export function getDefaultCCSlideShowProps() {
// Base 16:9 ratio dimensions
const baseWidth = 1280
const baseHeight = 720
// Add header height and spacing
const totalHeight = baseHeight +
CC_SLIDESHOW_STYLE_CONSTANTS.SLIDE_HEADER_HEIGHT + // Slideshow's own header
CC_SLIDESHOW_STYLE_CONSTANTS.SLIDE_SPACING * 2 + // Top and bottom spacing
CC_SLIDESHOW_STYLE_CONSTANTS.SLIDE_CONTENT_PADDING // Extra padding for content
return {
title: 'Slideshow',
w: baseWidth,
h: totalHeight,
headerColor: '#3e6589',
backgroundColor: '#0f0f0f',
isLocked: false,
currentSlideIndex: 0,
slidePattern: 'horizontal',
numSlides: 3,
slides: [],
}
}
export function getDefaultCCSlideProps() {
// Base 16:9 ratio dimensions
const baseWidth = 1280
const baseHeight = 720
// Add header height
const totalHeight = baseHeight + CC_BASE_STYLE_CONSTANTS.HEADER.height
return {
title: 'Slide',
w: baseWidth,
h: totalHeight,
headerColor: '#3e6589',
backgroundColor: '#0f0f0f',
isLocked: false,
imageData: '',
meta: {
text: '',
format: 'markdown'
}
}
}
export function getDefaultCCSlideLayoutBindingProps() {
return {
isMovingWithParent: false,
placeholder: false,
index: '0',
}
}
export function getDefaultCCYoutubeEmbedProps() {
const videoHeight = 450
const totalHeight = videoHeight + CC_BASE_STYLE_CONSTANTS.HEADER.height + (CC_BASE_STYLE_CONSTANTS.CONTENT.padding * 2)
return {
...getDefaultCCBaseProps(),
title: 'YouTube Video',
w: 800,
h: totalHeight,
headerColor: '#ff0000',
backgroundColor: '#0f0f0f',
isLocked: false,
video_url: 'https://www.youtube.com/watch?v=dQw4w9WgXcQ',
transcript: [],
transcriptVisible: false,
}
}
export const getDefaultCCSearchProps = () => ({
...getDefaultCCBaseProps(),
w: 400,
h: 500,
title: 'Search',
headerColor: '#1a73e8',
backgroundColor: '#ffffff',
query: '',
results: [],
isSearching: false,
})
export const getDefaultCCWebBrowserProps = () => ({
...getDefaultCCBaseProps(),
title: 'Web Browser',
w: 800,
h: 600,
headerColor: '#1a73e8',
backgroundColor: '#ffffff',
url: '',
history: [],
currentHistoryIndex: -1,
isLoading: false,
})

View File

@ -0,0 +1,118 @@
// Style constants used by all CC shapes
export const CC_BASE_STYLE_CONSTANTS = {
FONT_FAMILY: 'Inter, sans-serif',
FONT_SIZES: {
small: 12,
medium: 14,
large: 16,
},
// Container styles
CONTAINER: {
borderRadius: '4px',
borderWidth: '2px',
borderColor: '#e2e8f0',
boxShadow: '0 2px 4px var(--color-muted-1)',
},
HEADER: {
height: 32,
padding: 8,
borderRadius: 4,
},
CONTENT: {
padding: 16,
borderRadius: 8,
borderWidth: 2,
backgroundColor: 'white',
},
HANDLE: {
width: 8,
},
COLORS: {
primary: '#3e6589',
primary_dark: '#2e4a69',
secondary: '#718096',
secondary_dark: '#5a687a',
background: '#ffffff',
border: '#e2e8f0',
text: '#1a202c',
textLight: '#718096',
},
// Minimum dimensions
MIN_DIMENSIONS: {
width: 100,
height: 100,
},
} as const
// Calendar specific styles
export const CC_CALENDAR_STYLE_CONSTANTS = {
// Common button styles
COMMON_BUTTON: {
border: 'none',
borderRadius: '5px',
padding: '0.4em 1em',
fontSize: '0.95em',
textTransform: 'uppercase',
letterSpacing: '0.05em',
cursor: 'pointer',
transition: 'background-color 0.3s ease',
boxShadow: '0 4px 6px rgba(0, 0, 0, 0.1)',
},
// Application button styles
APPLICATION_BUTTON: {
backgroundColor: '#4f80ff',
color: '#fff',
},
// Option button styles
OPTION_BUTTON: {
backgroundColor: '#f0f4f9',
color: '#2c3e50',
border: '1px solid #ddd',
},
// Calendar event styles
EVENT: {
mainFrame: {
backgroundColor: 'transparent',
padding: '0px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
minHeight: '100%',
borderRadius: '4px',
},
title: {
fontSize: '1.1em',
fontWeight: 'normal',
textAlign: 'center',
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
opacity: 1,
padding: '0px 0px',
width: '100%',
letterSpacing: '0.02em',
margin: '0px 0px',
}
}
} as const
// Slideshow specific styles
export const CC_SLIDESHOW_STYLE_CONSTANTS = {
DEFAULT_SLIDE_WIDTH: 800,
DEFAULT_SLIDE_HEIGHT: 600,
SLIDE_HEADER_HEIGHT: 40,
SLIDE_HEADER_PADDING: 8,
SLIDE_CONTENT_PADDING: 16,
SLIDE_BORDER_RADIUS: 4,
SLIDE_BORDER_WIDTH: 1,
SLIDE_SPACING: 16,
SLIDE_COLORS: {
background: '#ffffff',
border: '#e2e8f0',
text: '#ffffff',
secondary: '#718096',
},
} as const

View File

@ -0,0 +1,4 @@
import { TLBaseShape } from 'tldraw'
import { CCBaseProps } from './cc-props'
export interface CCBaseShape extends TLBaseShape<string, CCBaseProps> {}

View File

@ -0,0 +1,411 @@
import {
Editor,
HTMLContainer,
Rectangle2d,
ShapeUtil,
TLDefaultColorTheme,
getDefaultColorTheme,
createShapeId
} from 'tldraw'
import {
AllNodeShapes
} from './graph-shape-types'
import {
AllRelationshipShapes
} from './graph-relationship-types'
import { getNodeComponent } from './nodeComponents';
import axios from '../../../axiosConfig';
import graphState from './graphStateUtil';
export const nodeTypeConfig = {
Developer: { shapeType: 'developer_node', color: 'light-blue' },
Teacher: { shapeType: 'teacher_node', color: 'light-green' },
User: { shapeType: 'user_node', color: 'light-green' },
TeacherTimetable: { shapeType: 'teacher_timetable_node', color: 'blue' },
TimetableLesson: { shapeType: 'timetable_lesson_node', color: 'light-blue' },
PlannedLesson: { shapeType: 'planned_lesson_node', color: 'light-green' },
School: { shapeType: 'school_node', color: 'grey' },
Calendar: { shapeType: 'calendar_node', color: 'violet' },
CalendarYear: { shapeType: 'calendar_year_node', color: 'red' },
CalendarMonth: { shapeType: 'calendar_month_node', color: 'light-violet' },
CalendarWeek: { shapeType: 'calendar_week_node', color: 'light-red' },
CalendarDay: { shapeType: 'calendar_day_node', color: 'light-blue' },
CalendarTimeChunk: { shapeType: 'calendar_time_chunk_node', color: 'blue' },
ScienceLab: { shapeType: 'science_lab_node', color: 'yellow' },
KeyStageSyllabus: { shapeType: 'key_stage_syllabus_node', color: 'grey' },
YearGroupSyllabus: { shapeType: 'year_group_syllabus_node', color: 'light-blue' },
CurriculumStructure: { shapeType: 'curriculum_structure_node', color: 'grey' },
Topic: { shapeType: 'topic_node', color: 'green' },
TopicLesson: { shapeType: 'topic_lesson_node', color: 'light-green' },
LearningStatement: { shapeType: 'learning_statement_node', color: 'light-blue' },
SchoolTimetable: { shapeType: 'school_timetable_node', color: 'grey' },
AcademicYear: { shapeType: 'academic_year_node', color: 'light-violet' },
AcademicTerm: { shapeType: 'academic_term_node', color: 'yellow' },
AcademicWeek: { shapeType: 'academic_week_node', color: 'orange' },
AcademicDay: { shapeType: 'academic_day_node', color: 'light-red' },
AcademicPeriod: { shapeType: 'academic_period_node', color: 'light-green' },
RegistrationPeriod: { shapeType: 'registration_period_node', color: 'light-green' },
PastoralStructure: { shapeType: 'pastoral_structure_node', color: 'grey' },
KeyStage: { shapeType: 'key_stage_node', color: 'blue' },
Department: { shapeType: 'department_node', color: 'light-blue' },
Room: { shapeType: 'room_node', color: 'violet' },
SubjectClass: { shapeType: 'subject_class_node', color: 'light-blue' },
};
const createNodeComponent = (shape: AllNodeShapes, theme: TLDefaultColorTheme, editor: Editor) => {
let isDragging = false;
let startX = 0;
let startY = 0;
const borderColor = theme.id === 'dark' ? 'white' : 'black'
const handlePointerDown = (e: React.PointerEvent) => {
e.preventDefault()
e.stopPropagation()
const rect = e.currentTarget.getBoundingClientRect()
const x = e.clientX - rect.left
const y = e.clientY - rect.top
// Define button areas
const openFileButtonArea = { x: 10, y: shape.props.h - 60, width: shape.props.w - 20, height: 25 }
const getConnectedNodesButtonArea = { x: 10, y: shape.props.h - 30, width: shape.props.w - 20, height: 25 }
if (isPointInRect(x, y, openFileButtonArea)) {
console.log('Clicked on Open File button')
loadTldrawFile(shape.props.path, editor)
} else if (isPointInRect(x, y, getConnectedNodesButtonArea)) {
console.log('Clicked on Get Connected Nodes button')
handleGetConnectedNodes()
} else if (isPointInShape(x, y, shape) && !isPointInRect(x, y, openFileButtonArea) && !isPointInRect(x, y, getConnectedNodesButtonArea)) {
console.log('Clicked on shape')
isDragging = true;
startX = e.clientX - shape.x;
startY = e.clientY - shape.y;
}
}
const handlePointerMove = (e: React.PointerEvent) => {
if (isDragging) {
const newX = e.clientX - startX;
const newY = e.clientY - startY;
editor.updateShape({
id: shape.id,
type: shape.type,
x: newX,
y: newY,
});
}
}
const handlePointerUp = (e: React.PointerEvent) => {
isDragging = false;
}
const isPointInShape = (x: number, y: number, shape: AllNodeShapes) => {
const bounds = editor.getShapeGeometry(shape).bounds
return x >= bounds.x && x <= bounds.x + bounds.width && y >= bounds.y && y <= bounds.y + bounds.height
}
const isPointInRect = (x: number, y: number, rect: { x: number, y: number, width: number, height: number }) => {
return x >= rect.x && x <= rect.x + rect.width && y >= rect.y && y <= rect.y + rect.height
}
let isFetchingConnectedNodes = false;
const handleGetConnectedNodes = async () => {
if (isFetchingConnectedNodes) {
console.log("WARNING! Already fetching connected nodes. Skipping...");
return;
}
isFetchingConnectedNodes = true;
console.log("Getting connected nodes for:", shape.props.unique_id);
try {
const response = await axios.get(`/api/database/tools/get-connected-nodes-and-edges?unique_id=${shape.props.unique_id}`);
console.log("Connected nodes response:", response.data);
if (response.data.status === "success") {
const mainNode = response.data.main_node;
const connectedNodes = response.data.connected_nodes;
const relationships = response.data.relationships;
// Add nodes to the graph
[mainNode, ...connectedNodes].forEach((node: any) => {
console.log("Node:", node);
const newShapeId = createShapeId(node.node_data.unique_id);
const doesShapeExist = editor.getShape(newShapeId);
if (!doesShapeExist) {
console.log("Creating new shape with ID:", newShapeId);
const nodeConfig = nodeTypeConfig[node.node_type as keyof typeof nodeTypeConfig];
if (nodeConfig) {
const newShape = {
id: newShapeId,
type: nodeConfig.shapeType,
x: 0,
y: 0,
props: {
color: nodeConfig.color,
...node.node_data
}
};
console.log("New shape:", newShape);
console.log("Creating shape:", newShape);
editor.createShape(newShape);
console.log("New shape created:", newShape);
const bounds = editor.getShapeGeometry(newShapeId).bounds;
console.log("Shape bounds:", bounds);
console.log("Updating shape with width:", bounds.w, "and height:", bounds.h);
newShape.props.w = bounds.w;
newShape.props.h = bounds.h;
console.log("Adding node to graphState:", newShape);
const shapeWithWidthAndHeight = {
...newShape,
w: bounds.w,
h: bounds.h
}
graphState.addNode(shapeWithWidthAndHeight);
console.log("Node added to graphState:", newShape);
} else {
console.log("WARNING! Node type not found:", node.node_type);
}
}
});
console.log("Updating shapes with dagre...");
graphState.setEditor(editor);
graphState.updateShapesWithDagre();
// Add edges to the graph
relationships.forEach((relationship: any) => {
graphState.addEdge(relationship.start_node.unique_id, relationship.end_node.unique_id);
});
// Create edge shapes
graphState.getEdges().forEach((edge: any) => {
console.log("WARNING! Cancelling createEdgeComponent()...");
// console.log("handleGetConnectedNodes(): Creating edge component for:", edge.v, edge.w);
// createEdgeComponent(edge.v, edge.w, editor);
});
console.log("Done!");
} else {
console.error('Error in response:', response.data.message);
}
} catch (error) {
console.error('Error fetching connected nodes:', error);
} finally {
isFetchingConnectedNodes = false;
}
};
const loadTldrawFile = async (path: string, editor: any) => {
console.log("Loading tldraw_file...")
try {
const response = await axios.get(`/api/database/tldraw_fs/get_tldraw_user_file${path}/tldraw_file.json`);
const fileContent = response.data;
console.log("File content:", fileContent);
if (fileContent && fileContent.document && fileContent.document.store) {
// Ensure the schema version is set
if (!fileContent.document.schema) {
console.log("!fileContent.document.schema")
fileContent.document.schema = { schemaVersion: 1 };
} else if (!fileContent.document.schema.schemaVersion) {
console.log("!fileContent.document.schema.schemaVersion")
fileContent.document.schema.schemaVersion = 1;
}
// Load the new content
console.log("Loading snapshot: ", fileContent)
editor.loadSnapshot(fileContent);
} else {
console.error('Invalid file content structure:', fileContent);
throw new Error('Invalid file content structure');
}
} catch (error) {
console.error('Error loading tldraw file:', error);
}
};
return (
<HTMLContainer
id={shape.id}
style={{
border: `1px solid ${borderColor}`,
borderRadius: '5px',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'space-between',
pointerEvents: 'all',
backgroundColor: theme[shape.props.color].semi,
color: theme[shape.props.color].solid,
boxShadow: '1px 1px 2px rgba(0, 0, 0, 0.1)',
transition: 'all 0.3s ease',
overflow: 'hidden',
padding: '10px',
height: shape.props.h,
}}
onPointerDown={handlePointerDown}
onPointerMove={handlePointerMove}
onPointerUp={handlePointerUp}
>
{getNodeComponent(shape, theme)}
<div style={{ display: 'flex', flexDirection: 'column', width: '100%', marginTop: '10px' }}>
<div
style={{
backgroundColor: theme[shape.props.color].solid,
color: theme[shape.props.color].semi,
padding: '5px',
borderRadius: '3px',
cursor: 'pointer',
marginBottom: '5px',
textAlign: 'center',
}}
onClick={() => loadTldrawFile(shape.props.path, editor)}
>
Open File
</div>
<div
style={{
backgroundColor: theme[shape.props.color].solid,
color: theme[shape.props.color].semi,
padding: '5px',
borderRadius: '3px',
cursor: 'pointer',
textAlign: 'center',
}}
onClick={handleGetConnectedNodes}
>
Get Connected Nodes
</div>
</div>
</HTMLContainer>
)
}
const createNodeIndicator = (shape: AllNodeShapes, editor: any) => {
const bounds = editor.getShapeGeometry(shape).bounds
const theme = getDefaultColorTheme({ isDarkMode: editor.user.getIsDarkMode() })
return (
<rect
x={0}
y={0}
width={bounds.width}
height={bounds.height}
fill="none"
stroke={theme[shape.props.color].solid}
strokeWidth={2}
rx={5}
ry={5}
/>
)
}
const createEdgeComponent = (sourceId: string, targetId: string, editor: any) => {
console.log("Creating edge component for:", sourceId, targetId)
const edge = {
type: 'general_relationship',
props: {
w: 200,
h: 300,
color: 'black',
__relationshiptype__: '',
source: sourceId,
target: targetId,
}
};
editor.createShape(edge);
graphState.addNode(edge);
};
export abstract class BaseNodeShapeUtil<T extends AllNodeShapes> extends ShapeUtil<T> {
static override type: string
static override props: any
static override migrations: any
override isAspectRatioLocked = (_shape: T) => true
override canResize = (_shape: T) => true
abstract override getDefaultProps(): T['props']
getGeometry(shape: T) {
return new Rectangle2d({
width: shape.props.w,
height: shape.props.h,
x: 0,
y: 0,
isFilled: true,
})
}
component(shape: T) {
const theme = getDefaultColorTheme({ isDarkMode: this.editor.user.getIsDarkMode() })
return createNodeComponent(shape, theme, this.editor)
}
indicator(shape: T) {
return createNodeIndicator(shape, this.editor)
}
onDrag = (shape: T, dx: number, dy: number) => {
return {
x: shape.x + dx,
y: shape.y + dy,
}
}
}
export abstract class BaseRelationshipShapeUtil<T extends AllRelationshipShapes> extends ShapeUtil<T> {
static override type: string
static override props: any
static override migrations: any
override isAspectRatioLocked = (_shape: T) => true
override canResize = (_shape: T) => true
abstract override getDefaultProps(): T['props']
getGeometry(shape: T) {
return new Rectangle2d({
width: shape.props.w,
height: shape.props.h,
x: 0,
y: 0,
isFilled: true,
});
}
component(shape: T) {
// Define how the edge is rendered
return (
<line
x1={shape.x}
y1={shape.y}
x2={shape.x}
y2={shape.y}
stroke={shape.props.color}
strokeWidth={2}
/>
);
}
indicator(shape: T) {
// Define the indicator for the edge
return (
<line
x1={shape.x}
y1={shape.y}
x2={shape.x}
y2={shape.y}
stroke={shape.props.color}
strokeWidth={2}
strokeDasharray="4 2"
/>
);
}
}

View File

@ -0,0 +1,23 @@
import { createShapePropsMigrationIds, createShapePropsMigrationSequence } from 'tldraw'
// Ensure each node type and its migrations are added separately
const generalRelationshipVersions = createShapePropsMigrationIds(
'general_relationship',
{
AddSomeProperty: 1,
}
);
export const generalRelationshipShapeMigrations = createShapePropsMigrationSequence({
sequence: [
{
id: generalRelationshipVersions.AddSomeProperty,
up(props) {
props.someProperty = 'some value'
},
down(props) {
delete props.someProperty
},
}
],
})

View File

@ -0,0 +1,20 @@
import { DefaultColorStyle, T, RecordProps } from 'tldraw'
import {
GeneralRelationshipShape
} from './graph-relationship-types'
// Base node shape props
export const baseRelationshipShapeProps = {
w: T.number,
h: T.number,
color: DefaultColorStyle,
__relationshiptype__: T.string,
source: T.string,
target: T.string,
}
// General relationship shape props
export const generalRelationshipShapeProps: RecordProps<GeneralRelationshipShape> = {
...baseRelationshipShapeProps,
}

View File

@ -0,0 +1,14 @@
import { TLBaseShape, TLDefaultColorStyle } from 'tldraw'
import {
GeneralRelationshipInterface
} from '../../../types/graph_relationship_types'
export type BaseRelationshipShape<T extends string, U> = TLBaseShape<T, {
w: number
h: number
color: TLDefaultColorStyle
} & U>;
export type AllRelationshipShapes = GeneralRelationshipShape;
export type GeneralRelationshipShape = BaseRelationshipShape<"general_relationship", GeneralRelationshipInterface>;

View File

@ -0,0 +1,749 @@
import { createShapePropsMigrationIds, createShapePropsMigrationSequence } from 'tldraw'
// Ensure each node type and its migrations are added separately
const userNodeVersions = createShapePropsMigrationIds(
'user_node',
{
AddSomeProperty: 1,
}
)
const developerNodeVersions = createShapePropsMigrationIds(
'developer_node',
{
AddSomeProperty: 1,
}
);
const teacherNodeVersions = createShapePropsMigrationIds(
'teacher_node',
{
AddSomeProperty: 1,
}
);
const studentNodeVersions = createShapePropsMigrationIds(
'student_node',
{
AddSomeProperty: 1,
}
);
export const userNodeShapeMigrations = createShapePropsMigrationSequence({
sequence: [
{
id: userNodeVersions.AddSomeProperty,
up(props) {
props.someProperty = 'some value'
},
down(props) {
delete props.someProperty
},
}
],
})
export const developerNodeShapeMigrations = createShapePropsMigrationSequence({
sequence: [
{
id: developerNodeVersions.AddSomeProperty,
up(props) {
props.someProperty = 'some value'
},
down(props) {
delete props.someProperty
},
}
],
})
export const teacherNodeShapeMigrations = createShapePropsMigrationSequence({
sequence: [
{
id: teacherNodeVersions.AddSomeProperty,
up(props) {
props.someProperty = 'some value'
},
down(props) {
delete props.someProperty
},
}
],
})
export const studentNodeShapeMigrations = createShapePropsMigrationSequence({
sequence: [
{
id: studentNodeVersions.AddSomeProperty,
up(props) {
props.someProperty = 'some value'
},
down(props) {
delete props.someProperty
},
}
],
})
// Calendar node shape migrations
const calendarNodeVersions = createShapePropsMigrationIds(
'calendar_node',
{
AddSomeProperty: 1,
}
)
const yearNodeVersions = createShapePropsMigrationIds(
'calendar_year_node',
{
AddSomeProperty: 1,
}
);
const monthNodeVersions = createShapePropsMigrationIds(
'calendar_month_node',
{
AddSomeProperty: 1,
}
);
const weekNodeVersions = createShapePropsMigrationIds(
'calendar_week_node',
{
AddSomeProperty: 1,
}
);
const dayNodeVersions = createShapePropsMigrationIds(
'calendar_day_node',
{
AddSomeProperty: 1,
}
);
const timeChunkNodeVersions = createShapePropsMigrationIds(
'calendar_time_chunk_node',
{
AddSomeProperty: 1,
}
);
export const calendarNodeShapeMigrations = createShapePropsMigrationSequence({
sequence: [
{
id: calendarNodeVersions.AddSomeProperty,
up(props) {
props.someProperty = 'some value'
},
down(props) {
delete props.someProperty
},
}
],
})
export const yearNodeShapeMigrations = createShapePropsMigrationSequence({
sequence: [
{
id: yearNodeVersions.AddSomeProperty,
up(props) {
props.someProperty = 'some value'
},
down(props) {
delete props.someProperty
},
}
],
})
export const monthNodeShapeMigrations = createShapePropsMigrationSequence({
sequence: [
{
id: monthNodeVersions.AddSomeProperty,
up(props) {
props.someProperty = 'some value'
},
down(props) {
delete props.someProperty
},
}
],
})
export const weekNodeShapeMigrations = createShapePropsMigrationSequence({
sequence: [
{
id: weekNodeVersions.AddSomeProperty,
up(props) {
props.someProperty = 'some value'
},
down(props) {
delete props.someProperty
},
}
],
})
export const dayNodeShapeMigrations = createShapePropsMigrationSequence({
sequence: [
{
id: dayNodeVersions.AddSomeProperty,
up(props) {
props.someProperty = 'some value'
},
down(props) {
delete props.someProperty
},
}
],
})
export const timeChunkNodeShapeMigrations = createShapePropsMigrationSequence({
sequence: [
{
id: timeChunkNodeVersions.AddSomeProperty,
up(props) {
props.someProperty = 'some value'
},
down(props) {
delete props.someProperty
},
}
],
})
const schoolNodeVersions = createShapePropsMigrationIds(
'school_node',
{
AddSomeProperty: 1,
}
)
const departmentNodeVersions = createShapePropsMigrationIds(
'department_node',
{
AddSomeProperty: 1,
}
);
const roomNodeVersions = createShapePropsMigrationIds(
'room_node',
{
AddSomeProperty: 1,
}
);
const subjectClassNodeVersions = createShapePropsMigrationIds(
'subject_class_node',
{
AddSomeProperty: 1,
}
);
export const schoolNodeShapeMigrations = createShapePropsMigrationSequence({
sequence: [
{
id: schoolNodeVersions.AddSomeProperty,
up(props) {
props.someProperty = 'some value'
},
down(props) {
delete props.someProperty
},
}
],
})
export const departmentNodeShapeMigrations = createShapePropsMigrationSequence({
sequence: [
{
id: departmentNodeVersions.AddSomeProperty,
up(props) {
props.someProperty = 'some value'
},
down(props) {
delete props.someProperty
},
}
],
})
export const roomNodeShapeMigrations = createShapePropsMigrationSequence({
sequence: [
{
id: roomNodeVersions.AddSomeProperty,
up(props) {
props.someProperty = 'some value'
},
down(props) {
delete props.someProperty
},
}
],
})
export const subjectClassNodeShapeMigrations = createShapePropsMigrationSequence({
sequence: [
{
id: subjectClassNodeVersions.AddSomeProperty,
up(props) {
props.someProperty = 'some value'
},
down(props) {
delete props.someProperty
},
}
],
})
const pastoralStructureNodeVersions = createShapePropsMigrationIds(
'pastoral_structure_node',
{
AddSomeProperty: 1,
}
)
const yearGroupNodeVersions = createShapePropsMigrationIds(
'year_group_node',
{
AddSomeProperty: 1,
}
);
const curriculumStructureNodeVersions = createShapePropsMigrationIds(
'curriculum_structure_node',
{
AddSomeProperty: 1,
}
);
const keyStageNodeVersions = createShapePropsMigrationIds(
'key_stage_node',
{
AddSomeProperty: 1,
}
);
const keyStageSyllabusNodeVersions = createShapePropsMigrationIds(
'key_stage_syllabus_node',
{
AddSomeProperty: 1,
}
);
const yearGroupSyllabusNodeVersions = createShapePropsMigrationIds(
'year_group_syllabus_node',
{
AddSomeProperty: 1,
}
);
const subjectNodeVersions = createShapePropsMigrationIds(
'subject_node',
{
AddSomeProperty: 1,
}
);
const topicNodeVersions = createShapePropsMigrationIds(
'topic_node',
{
AddSomeProperty: 1,
}
);
const topicLessonNodeVersions = createShapePropsMigrationIds(
'topic_lesson_node',
{
AddSomeProperty: 1,
}
);
const learningStatementNodeVersions = createShapePropsMigrationIds(
'learning_statement_node',
{
AddSomeProperty: 1,
}
);
const scienceLabNodeVersions = createShapePropsMigrationIds(
'science_lab_node',
{
AddSomeProperty: 1,
}
);
export const pastoralStructureNodeShapeMigrations = createShapePropsMigrationSequence({
sequence: [
{
id: pastoralStructureNodeVersions.AddSomeProperty,
up(props) {
props.someProperty = 'some value'
},
down(props) {
delete props.someProperty
},
}
],
})
export const yearGroupNodeShapeMigrations = createShapePropsMigrationSequence({
sequence: [
{
id: yearGroupNodeVersions.AddSomeProperty,
up(props) {
props.someProperty = 'some value'
},
down(props) {
delete props.someProperty
},
}
],
})
export const curriculumStructureNodeShapeMigrations = createShapePropsMigrationSequence({
sequence: [
{
id: curriculumStructureNodeVersions.AddSomeProperty,
up(props) {
props.someProperty = 'some value'
},
down(props) {
delete props.someProperty
},
}
],
})
export const keyStageNodeShapeMigrations = createShapePropsMigrationSequence({
sequence: [
{
id: keyStageNodeVersions.AddSomeProperty,
up(props) {
props.someProperty = 'some value'
},
down(props) {
delete props.someProperty
},
}
],
})
export const keyStageSyllabusNodeShapeMigrations = createShapePropsMigrationSequence({
sequence: [
{
id: keyStageSyllabusNodeVersions.AddSomeProperty,
up(props) {
props.someProperty = 'some value'
},
down(props) {
delete props.someProperty
},
}
],
})
export const yearGroupSyllabusNodeShapeMigrations = createShapePropsMigrationSequence({
sequence: [
{
id: yearGroupSyllabusNodeVersions.AddSomeProperty,
up(props) {
props.someProperty = 'some value'
},
down(props) {
delete props.someProperty
},
}
],
})
export const subjectNodeShapeMigrations = createShapePropsMigrationSequence({
sequence: [
{
id: subjectNodeVersions.AddSomeProperty,
up(props) {
props.someProperty = 'some value'
},
down(props) {
delete props.someProperty
},
}
],
})
export const topicNodeShapeMigrations = createShapePropsMigrationSequence({
sequence: [
{
id: topicNodeVersions.AddSomeProperty,
up(props) {
props.someProperty = 'some value'
},
down(props) {
delete props.someProperty
},
}
],
})
export const topicLessonNodeShapeMigrations = createShapePropsMigrationSequence({
sequence: [
{
id: topicLessonNodeVersions.AddSomeProperty,
up(props) {
props.someProperty = 'some value'
},
down(props) {
delete props.someProperty
},
}
],
})
export const learningStatementNodeShapeMigrations = createShapePropsMigrationSequence({
sequence: [
{
id: learningStatementNodeVersions.AddSomeProperty,
up(props) {
props.someProperty = 'some value'
},
down(props) {
delete props.someProperty
},
}
],
})
export const scienceLabNodeShapeMigrations = createShapePropsMigrationSequence({
sequence: [
{
id: scienceLabNodeVersions.AddSomeProperty,
up(props) {
props.someProperty = 'some value'
},
down(props) {
delete props.someProperty
},
}
],
})
const schoolTimetableNodeVersions = createShapePropsMigrationIds(
'school_timetable_node',
{
AddSomeProperty: 1,
}
)
const academicYearNodeVersions = createShapePropsMigrationIds(
'academic_year_node',
{
AddSomeProperty: 1,
}
);
const academicTermNodeVersions = createShapePropsMigrationIds(
'academic_term_node',
{
AddSomeProperty: 1,
}
);
const academicWeekNodeVersions = createShapePropsMigrationIds(
'academic_week_node',
{
AddSomeProperty: 1,
}
);
const academicDayNodeVersions = createShapePropsMigrationIds(
'academic_day_node',
{
AddSomeProperty: 1,
}
);
const academicPeriodNodeVersions = createShapePropsMigrationIds(
'academic_period_node',
{
AddSomeProperty: 1,
}
);
const registrationPeriodNodeVersions = createShapePropsMigrationIds(
'registration_period_node',
{
AddSomeProperty: 1,
}
);
export const schoolTimetableNodeShapeMigrations = createShapePropsMigrationSequence({
sequence: [
{
id: schoolTimetableNodeVersions.AddSomeProperty,
up(props) {
props.someProperty = 'some value'
},
down(props) {
delete props.someProperty
},
}
],
})
export const academicYearNodeShapeMigrations = createShapePropsMigrationSequence({
sequence: [
{
id: academicYearNodeVersions.AddSomeProperty,
up(props) {
props.someProperty = 'some value'
},
down(props) {
delete props.someProperty
},
}
],
})
export const academicTermNodeShapeMigrations = createShapePropsMigrationSequence({
sequence: [
{
id: academicTermNodeVersions.AddSomeProperty,
up(props) {
props.someProperty = 'some value'
},
down(props) {
delete props.someProperty
},
}
],
})
export const academicWeekNodeShapeMigrations = createShapePropsMigrationSequence({
sequence: [
{
id: academicWeekNodeVersions.AddSomeProperty,
up(props) {
props.someProperty = 'some value'
},
down(props) {
delete props.someProperty
},
}
],
})
export const academicDayNodeShapeMigrations = createShapePropsMigrationSequence({
sequence: [
{
id: academicDayNodeVersions.AddSomeProperty,
up(props) {
props.someProperty = 'some value'
},
down(props) {
delete props.someProperty
},
}
],
})
export const academicPeriodNodeShapeMigrations = createShapePropsMigrationSequence({
sequence: [
{
id: academicPeriodNodeVersions.AddSomeProperty,
up(props) {
props.someProperty = 'some value'
},
down(props) {
delete props.someProperty
},
}
],
})
export const registrationPeriodNodeShapeMigrations = createShapePropsMigrationSequence({
sequence: [
{
id: registrationPeriodNodeVersions.AddSomeProperty,
up(props) {
props.someProperty = 'some value'
},
down(props) {
delete props.someProperty
},
}
],
})
const teacherTimetableNodeVersions = createShapePropsMigrationIds(
'teacher_timetable_node',
{
AddSomeProperty: 1,
}
)
const timetableLessonNodeVersions = createShapePropsMigrationIds(
'timetable_lesson_node',
{
AddSomeProperty: 1,
}
);
const plannedLessonNodeVersions = createShapePropsMigrationIds(
'planned_lesson_node',
{
AddSomeProperty: 1,
}
);
export const teacherTimetableNodeShapeMigrations = createShapePropsMigrationSequence({
sequence: [
{
id: teacherTimetableNodeVersions.AddSomeProperty,
up(props) {
props.someProperty = 'some value'
},
down(props) {
delete props.someProperty
},
}
],
})
export const timetableLessonNodeShapeMigrations = createShapePropsMigrationSequence({
sequence: [
{
id: timetableLessonNodeVersions.AddSomeProperty,
up(props) {
props.someProperty = 'some value'
},
down(props) {
delete props.someProperty
},
}
],
})
export const plannedLessonNodeShapeMigrations = createShapePropsMigrationSequence({
sequence: [
{
id: plannedLessonNodeVersions.AddSomeProperty,
up(props) {
props.someProperty = 'some value'
},
down(props) {
delete props.someProperty
},
}
],
})

View File

@ -0,0 +1,332 @@
import { DefaultColorStyle, RecordProps, T } from 'tldraw'
import {
UserNodeShape,
DeveloperNodeShape,
TeacherNodeShape,
StudentNodeShape,
CalendarNodeShape,
CalendarYearNodeShape,
CalendarMonthNodeShape,
CalendarWeekNodeShape,
CalendarDayNodeShape,
CalendarTimeChunkNodeShape,
SchoolNodeShape,
DepartmentNodeShape,
RoomNodeShape,
SubjectClassNodeShape,
PastoralStructureNodeShape,
YearGroupNodeShape,
CurriculumStructureNodeShape,
KeyStageNodeShape,
KeyStageSyllabusNodeShape,
YearGroupSyllabusNodeShape,
SubjectNodeShape,
TopicNodeShape,
TopicLessonNodeShape,
LearningStatementNodeShape,
ScienceLabNodeShape,
SchoolTimetableNodeShape,
AcademicYearNodeShape,
AcademicTermNodeShape,
AcademicWeekNodeShape,
AcademicDayNodeShape,
AcademicPeriodNodeShape,
RegistrationPeriodNodeShape,
TeacherTimetableNodeShape,
TimetableLessonNodeShape,
PlannedLessonNodeShape,
} from './graph-shape-types'
// Base node shape props
const baseNodeShapeProps = {
w: T.number,
h: T.number,
color: DefaultColorStyle,
__primarylabel__: T.string,
unique_id: T.string,
path: T.string,
created: T.string,
merged: T.string,
}
export const userNodeShapeProps: RecordProps<UserNodeShape> = {
...baseNodeShapeProps,
user_id: T.string,
user_name: T.string,
user_email: T.string,
worker_node_data: T.string,
user_type: T.string,
}
export const developerNodeShapeProps: RecordProps<DeveloperNodeShape> = {
...baseNodeShapeProps,
user_id: T.string,
user_name: T.string,
user_email: T.string,
user_type: T.string,
}
export const teacherNodeShapeProps: RecordProps<TeacherNodeShape> = {
...baseNodeShapeProps,
teacher_code: T.string,
teacher_name_formal: T.string,
teacher_email: T.string,
worker_db_name: T.string,
}
export const studentNodeShapeProps: RecordProps<StudentNodeShape> = {
...baseNodeShapeProps,
student_code: T.string,
student_name_formal: T.string,
student_email: T.string,
worker_db_name: T.string,
}
// Calendar node shape props
export const calendarNodeShapeProps: RecordProps<CalendarNodeShape> = {
...baseNodeShapeProps,
name: T.string,
start_date: T.string,
end_date: T.string,
}
export const calendarYearNodeShapeProps: RecordProps<CalendarYearNodeShape> = {
...baseNodeShapeProps,
year: T.string,
}
export const calendarMonthNodeShapeProps: RecordProps<CalendarMonthNodeShape> = {
...baseNodeShapeProps,
year: T.string,
month: T.string,
month_name: T.string,
}
export const calendarWeekNodeShapeProps: RecordProps<CalendarWeekNodeShape> = {
...baseNodeShapeProps,
start_date: T.string,
week_number: T.string,
iso_week: T.string,
}
export const calendarDayNodeShapeProps: RecordProps<CalendarDayNodeShape> = {
...baseNodeShapeProps,
date: T.string,
day_of_week: T.string,
iso_day: T.string,
}
export const calendarTimeChunkNodeShapeProps: RecordProps<CalendarTimeChunkNodeShape> = {
...baseNodeShapeProps,
start_time: T.string,
end_time: T.string,
}
// School
export const schoolNodeShapeProps: RecordProps<SchoolNodeShape> = {
...baseNodeShapeProps,
school_name: T.string,
school_website: T.string,
school_uuid: T.string,
}
export const departmentNodeShapeProps: RecordProps<DepartmentNodeShape> = {
...baseNodeShapeProps,
department_name: T.string,
}
export const roomNodeShapeProps: RecordProps<RoomNodeShape> = {
...baseNodeShapeProps,
room_code: T.string,
room_name: T.string,
}
export const subjectClassNodeShapeProps: RecordProps<SubjectClassNodeShape> = {
...baseNodeShapeProps,
subject_class_code: T.string,
year_group: T.string,
subject: T.string,
subject_code: T.string,
}
// Curriculum
export const pastoralStructureNodeShapeProps: RecordProps<PastoralStructureNodeShape> = {
...baseNodeShapeProps,
}
export const yearGroupNodeShapeProps: RecordProps<YearGroupNodeShape> = {
...baseNodeShapeProps,
year_group: T.string,
year_group_name: T.string,
}
export const curriculumStructureNodeShapeProps: RecordProps<CurriculumStructureNodeShape> = {
...baseNodeShapeProps,
}
export const keyStageNodeShapeProps: RecordProps<KeyStageNodeShape> = {
...baseNodeShapeProps,
key_stage_name: T.string,
key_stage: T.string,
}
export const keyStageSyllabusNodeShapeProps: RecordProps<KeyStageSyllabusNodeShape> = {
...baseNodeShapeProps,
ks_syllabus_id: T.string,
ks_syllabus_name: T.string,
ks_syllabus_key_stage: T.string,
ks_syllabus_subject: T.string,
ks_syllabus_subject_code: T.string,
}
export const yearGroupSyllabusNodeShapeProps: RecordProps<YearGroupSyllabusNodeShape> = {
...baseNodeShapeProps,
yr_syllabus_id: T.string,
yr_syllabus_name: T.string,
yr_syllabus_year_group: T.string,
yr_syllabus_subject: T.string,
yr_syllabus_subject_code: T.string,
}
export const subjectNodeShapeProps: RecordProps<SubjectNodeShape> = {
...baseNodeShapeProps,
subject_code: T.string,
subject_name: T.string,
}
export const topicNodeShapeProps: RecordProps<TopicNodeShape> = {
...baseNodeShapeProps,
topic_id: T.string,
topic_title: T.string,
total_number_of_lessons_for_topic: T.string,
topic_type: T.string,
topic_assessment_type: T.string,
}
export const topicLessonNodeShapeProps: RecordProps<TopicLessonNodeShape> = {
...baseNodeShapeProps,
topic_lesson_id: T.string,
topic_lesson_title: T.string,
topic_lesson_type: T.string,
topic_lesson_length: T.string,
topic_lesson_suggested_activities: T.string,
topic_lesson_skills_learned: T.string,
topic_lesson_weblinks: T.string,
}
export const learningStatementNodeShapeProps: RecordProps<LearningStatementNodeShape> = {
...baseNodeShapeProps,
lesson_learning_statement_id: T.string,
lesson_learning_statement: T.string,
lesson_learning_statement_type: T.string,
}
export const scienceLabNodeShapeProps: RecordProps<ScienceLabNodeShape> = {
...baseNodeShapeProps,
science_lab_id: T.string,
science_lab_title: T.string,
science_lab_summary: T.string,
science_lab_requirements: T.string,
science_lab_procedure: T.string,
science_lab_safety: T.string,
science_lab_weblinks: T.string,
}
// School Timetable
export const schoolTimetableNodeShapeProps: RecordProps<SchoolTimetableNodeShape> = {
...baseNodeShapeProps,
start_date: T.string,
end_date: T.string,
}
export const academicYearNodeShapeProps: RecordProps<AcademicYearNodeShape> = {
...baseNodeShapeProps,
year: T.string,
}
export const academicTermNodeShapeProps: RecordProps<AcademicTermNodeShape> = {
...baseNodeShapeProps,
term_name: T.string,
term_number: T.string,
start_date: T.string,
end_date: T.string,
}
export const academicWeekNodeShapeProps: RecordProps<AcademicWeekNodeShape> = {
...baseNodeShapeProps,
academic_week_number: T.string,
start_date: T.string,
week_type: T.string,
}
export const academicDayNodeShapeProps: RecordProps<AcademicDayNodeShape> = {
...baseNodeShapeProps,
academic_day: T.string,
date: T.string,
day_of_week: T.string,
day_type: T.string,
}
export const academicPeriodNodeShapeProps: RecordProps<AcademicPeriodNodeShape> = {
...baseNodeShapeProps,
name: T.string,
date: T.string,
start_time: T.string,
end_time: T.string,
period_code: T.string,
}
export const registrationPeriodNodeShapeProps: RecordProps<RegistrationPeriodNodeShape> = {
...baseNodeShapeProps,
name: T.string,
date: T.string,
start_time: T.string,
end_time: T.string,
period_code: T.string,
}
// Teacher Timetable
export const teacherTimetableNodeShapeProps: RecordProps<TeacherTimetableNodeShape> = {
...baseNodeShapeProps,
}
export const timetableLessonNodeShapeProps: RecordProps<TimetableLessonNodeShape> = {
...baseNodeShapeProps,
subject_class: T.string,
date: T.string,
start_time: T.string,
end_time: T.string,
period_code: T.string,
}
export const plannedLessonNodeShapeProps: RecordProps<PlannedLessonNodeShape> = {
...baseNodeShapeProps,
date: T.string,
start_time: T.string,
end_time: T.string,
period_code: T.string,
subject_class: T.string,
year_group: T.string,
subject: T.string,
teacher_code: T.string,
planning_status: T.string,
topic_code: T.string.optional().nullable(),
topic_name: T.string.optional().nullable(),
lesson_code: T.string.optional().nullable(),
lesson_name: T.string.optional().nullable(),
learning_statement_codes: T.string.optional().nullable(),
learning_statements: T.string.optional().nullable(),
learning_resource_codes: T.string.optional().nullable(),
learning_resources: T.string.optional().nullable(),
}

View File

@ -0,0 +1,94 @@
import { TLBaseShape, TLDefaultColorStyle } from 'tldraw'
import {
UserNodeInterface,
DeveloperNodeInterface,
TeacherNodeInterface,
StudentNodeInterface,
CalendarNodeInterface,
CalendarYearNodeInterface,
CalendarMonthNodeInterface,
CalendarWeekNodeInterface,
CalendarDayNodeInterface,
CalendarTimeChunkNodeInterface,
SchoolNodeInterface,
DepartmentNodeInterface,
RoomNodeInterface,
SubjectClassNodeInterface,
PastoralStructureNodeInterface,
YearGroupNodeInterface,
CurriculumStructureNodeInterface,
KeyStageNodeInterface,
KeyStageSyllabusNodeInterface,
YearGroupSyllabusNodeInterface,
SubjectNodeInterface,
TopicNodeInterface,
TopicLessonNodeInterface,
LearningStatementNodeInterface,
ScienceLabNodeInterface,
SchoolTimetableNodeInterface,
AcademicYearNodeInterface,
AcademicTermNodeInterface,
AcademicWeekNodeInterface,
AcademicDayNodeInterface,
AcademicPeriodNodeInterface,
RegistrationPeriodNodeInterface,
TeacherTimetableNodeInterface,
TimetableLessonNodeInterface,
PlannedLessonNodeInterface
} from '../../../types/graph_node_types';
export type BaseNodeShape<T extends string, U> = TLBaseShape<T, {
w: number
h: number
color: TLDefaultColorStyle
} & U>;
export type AllNodeShapes = UserNodeShape | DeveloperNodeShape | TeacherNodeShape | StudentNodeShape | CalendarNodeShape | CalendarYearNodeShape | CalendarMonthNodeShape | CalendarWeekNodeShape | CalendarDayNodeShape | CalendarTimeChunkNodeShape | ScienceLabNodeShape | KeyStageSyllabusNodeShape | YearGroupNodeShape | YearGroupSyllabusNodeShape | CurriculumStructureNodeShape | TopicNodeShape | TopicLessonNodeShape | LearningStatementNodeShape | SchoolNodeShape | TeacherTimetableNodeShape | TimetableLessonNodeShape | PlannedLessonNodeShape | SchoolTimetableNodeShape | SubjectClassNodeShape | SubjectNodeShape | AcademicDayNodeShape | AcademicWeekNodeShape | AcademicYearNodeShape | AcademicTermNodeShape | AcademicPeriodNodeShape | RegistrationPeriodNodeShape | PastoralStructureNodeShape | KeyStageNodeShape | RoomNodeShape | DepartmentNodeShape;
// User entity node shapes
export type UserNodeShape = BaseNodeShape<"user_node", UserNodeInterface>;
export type DeveloperNodeShape = BaseNodeShape<"developer_node", DeveloperNodeInterface>;
export type TeacherNodeShape = BaseNodeShape<"teacher_node", TeacherNodeInterface>;
export type StudentNodeShape = BaseNodeShape<"student_node", StudentNodeInterface>;
// Calendar node shapes
export type CalendarNodeShape = BaseNodeShape<"calendar_node", CalendarNodeInterface>;
export type CalendarYearNodeShape = BaseNodeShape<"calendar_year_node", CalendarYearNodeInterface>;
export type CalendarMonthNodeShape = BaseNodeShape<"calendar_month_node", CalendarMonthNodeInterface>;
export type CalendarWeekNodeShape = BaseNodeShape<"calendar_week_node", CalendarWeekNodeInterface>;
export type CalendarDayNodeShape = BaseNodeShape<"calendar_day_node", CalendarDayNodeInterface>;
export type CalendarTimeChunkNodeShape = BaseNodeShape<"calendar_time_chunk_node", CalendarTimeChunkNodeInterface>;
// School entity node shapes
export type SchoolNodeShape = BaseNodeShape<"school_node", SchoolNodeInterface>;
export type DepartmentNodeShape = BaseNodeShape<"department_node", DepartmentNodeInterface>;
export type RoomNodeShape = BaseNodeShape<"room_node", RoomNodeInterface>;
export type SubjectClassNodeShape = BaseNodeShape<"subject_class_node", SubjectClassNodeInterface>;
// Curriculum entity node shapes
export type PastoralStructureNodeShape = BaseNodeShape<"pastoral_structure_node", PastoralStructureNodeInterface>;
export type YearGroupNodeShape = BaseNodeShape<"year_group_node", YearGroupNodeInterface>;
export type CurriculumStructureNodeShape = BaseNodeShape<"curriculum_structure_node", CurriculumStructureNodeInterface>;
export type KeyStageNodeShape = BaseNodeShape<"key_stage_node", KeyStageNodeInterface>;
export type KeyStageSyllabusNodeShape = BaseNodeShape<"key_stage_syllabus_node", KeyStageSyllabusNodeInterface>;
export type YearGroupSyllabusNodeShape = BaseNodeShape<"year_group_syllabus_node", YearGroupSyllabusNodeInterface>;
export type SubjectNodeShape = BaseNodeShape<"subject_node", SubjectNodeInterface>;
export type TopicNodeShape = BaseNodeShape<"topic_node", TopicNodeInterface>;
export type TopicLessonNodeShape = BaseNodeShape<"topic_lesson_node", TopicLessonNodeInterface>;
export type LearningStatementNodeShape = BaseNodeShape<"learning_statement_node", LearningStatementNodeInterface>;
export type ScienceLabNodeShape = BaseNodeShape<"science_lab_node", ScienceLabNodeInterface>;
// School timetable entity node shapes
export type SchoolTimetableNodeShape = BaseNodeShape<"school_timetable_node", SchoolTimetableNodeInterface>;
export type AcademicYearNodeShape = BaseNodeShape<"academic_year_node", AcademicYearNodeInterface>;
export type AcademicTermNodeShape = BaseNodeShape<"academic_term_node", AcademicTermNodeInterface>;
export type AcademicWeekNodeShape = BaseNodeShape<"academic_week_node", AcademicWeekNodeInterface>;
export type AcademicDayNodeShape = BaseNodeShape<"academic_day_node", AcademicDayNodeInterface>;
export type AcademicPeriodNodeShape = BaseNodeShape<"academic_period_node", AcademicPeriodNodeInterface>;
export type RegistrationPeriodNodeShape = BaseNodeShape<"registration_period_node", RegistrationPeriodNodeInterface>;
// Teacher timetable entity node shapes
export type TeacherTimetableNodeShape = BaseNodeShape<"teacher_timetable_node", TeacherTimetableNodeInterface>;
export type TimetableLessonNodeShape = BaseNodeShape<"timetable_lesson_node", TimetableLessonNodeInterface>;
export type PlannedLessonNodeShape = BaseNodeShape<"planned_lesson_node", PlannedLessonNodeInterface>;

View File

@ -0,0 +1,971 @@
import { BaseNodeShapeUtil, BaseRelationshipShapeUtil } from './baseNodeShapeUtil'
import {
UserNodeShape,
DeveloperNodeShape,
TeacherNodeShape,
StudentNodeShape,
CalendarNodeShape,
CalendarYearNodeShape,
CalendarMonthNodeShape,
CalendarWeekNodeShape,
CalendarDayNodeShape,
CalendarTimeChunkNodeShape,
ScienceLabNodeShape,
KeyStageSyllabusNodeShape,
YearGroupNodeShape,
YearGroupSyllabusNodeShape,
CurriculumStructureNodeShape,
TopicNodeShape,
TopicLessonNodeShape,
LearningStatementNodeShape,
SchoolNodeShape,
TeacherTimetableNodeShape,
TimetableLessonNodeShape,
PlannedLessonNodeShape,
SchoolTimetableNodeShape,
SubjectClassNodeShape,
SubjectNodeShape,
AcademicDayNodeShape,
AcademicWeekNodeShape,
AcademicYearNodeShape,
AcademicTermNodeShape,
AcademicPeriodNodeShape,
RegistrationPeriodNodeShape,
PastoralStructureNodeShape,
KeyStageNodeShape,
RoomNodeShape,
DepartmentNodeShape,
} from './graph-shape-types'
import {
userNodeShapeProps,
developerNodeShapeProps,
teacherNodeShapeProps,
studentNodeShapeProps,
calendarNodeShapeProps,
calendarYearNodeShapeProps,
calendarMonthNodeShapeProps,
calendarWeekNodeShapeProps,
calendarDayNodeShapeProps,
calendarTimeChunkNodeShapeProps,
scienceLabNodeShapeProps,
keyStageSyllabusNodeShapeProps,
yearGroupNodeShapeProps,
yearGroupSyllabusNodeShapeProps,
curriculumStructureNodeShapeProps,
topicNodeShapeProps,
topicLessonNodeShapeProps,
learningStatementNodeShapeProps,
schoolNodeShapeProps,
teacherTimetableNodeShapeProps,
timetableLessonNodeShapeProps,
plannedLessonNodeShapeProps,
schoolTimetableNodeShapeProps,
subjectClassNodeShapeProps,
subjectNodeShapeProps,
academicDayNodeShapeProps,
academicWeekNodeShapeProps,
academicYearNodeShapeProps,
academicTermNodeShapeProps,
academicPeriodNodeShapeProps,
registrationPeriodNodeShapeProps,
pastoralStructureNodeShapeProps,
keyStageNodeShapeProps,
departmentNodeShapeProps,
roomNodeShapeProps
} from './graph-shape-props'
import {
userNodeShapeMigrations,
developerNodeShapeMigrations,
teacherNodeShapeMigrations,
studentNodeShapeMigrations,
calendarNodeShapeMigrations,
yearNodeShapeMigrations,
monthNodeShapeMigrations,
weekNodeShapeMigrations,
dayNodeShapeMigrations,
timeChunkNodeShapeMigrations,
keyStageSyllabusNodeShapeMigrations,
yearGroupNodeShapeMigrations,
yearGroupSyllabusNodeShapeMigrations,
curriculumStructureNodeShapeMigrations,
topicNodeShapeMigrations,
topicLessonNodeShapeMigrations,
learningStatementNodeShapeMigrations,
scienceLabNodeShapeMigrations,
schoolNodeShapeMigrations,
teacherTimetableNodeShapeMigrations,
timetableLessonNodeShapeMigrations,
plannedLessonNodeShapeMigrations,
schoolTimetableNodeShapeMigrations,
subjectClassNodeShapeMigrations,
subjectNodeShapeMigrations,
academicDayNodeShapeMigrations,
academicWeekNodeShapeMigrations,
academicYearNodeShapeMigrations,
academicTermNodeShapeMigrations,
academicPeriodNodeShapeMigrations,
registrationPeriodNodeShapeMigrations,
pastoralStructureNodeShapeMigrations,
roomNodeShapeMigrations,
departmentNodeShapeMigrations,
keyStageNodeShapeMigrations,
} from './graph-shape-migrations'
import { GeneralRelationshipShape } from './graph-relationship-types'
import { generalRelationshipShapeProps } from './graph-relationship-props'
import { generalRelationshipShapeMigrations } from './graph-relationship-migrations'
// User Nodes
export class UserNodeShapeUtil extends BaseNodeShapeUtil<UserNodeShape> {
static override type = 'user_node' as const
static override props = userNodeShapeProps
static override migrations = userNodeShapeMigrations
getDefaultProps(): UserNodeShape['props'] {
return {
w: 200,
h: 200,
color: 'blue',
__primarylabel__: 'User',
unique_id: '',
user_name: '',
user_email: '',
user_type: '',
user_id: '',
worker_node_data: '',
path: '',
created: '',
merged: '',
}
}
}
export class DeveloperNodeShapeUtil extends BaseNodeShapeUtil<DeveloperNodeShape> {
static override type = 'developer_node' as const
static override props = developerNodeShapeProps
static override migrations = developerNodeShapeMigrations
getDefaultProps(): DeveloperNodeShape['props'] {
return {
w: 200,
h: 200,
color: 'white',
__primarylabel__: 'Developer',
unique_id: '',
user_name: '',
user_email: '',
user_type: '',
user_id: '',
path: '',
created: '',
merged: '',
}
}
}
export class TeacherNodeShapeUtil extends BaseNodeShapeUtil<TeacherNodeShape> {
static override type = 'teacher_node' as const
static override props = teacherNodeShapeProps
static override migrations = teacherNodeShapeMigrations
getDefaultProps(): TeacherNodeShape['props'] {
return {
w: 200,
h: 200,
color: 'white',
__primarylabel__: 'Teacher',
unique_id: '',
teacher_code: '',
teacher_name_formal: '',
teacher_email: '',
worker_db_name: '',
path: '',
created: '',
merged: '',
}
}
}
export class StudentNodeShapeUtil extends BaseNodeShapeUtil<StudentNodeShape> {
static override type = 'student_node' as const
static override props = studentNodeShapeProps
static override migrations = studentNodeShapeMigrations
getDefaultProps(): StudentNodeShape['props'] {
return {
w: 200,
h: 200,
color: 'white',
__primarylabel__: 'Student',
unique_id: '',
student_code: '',
student_name_formal: '',
student_email: '',
worker_db_name: '',
path: '',
created: '',
merged: '',
}
}
}
// Calendar Nodes
export class CalendarNodeShapeUtil extends BaseNodeShapeUtil<CalendarNodeShape> {
static override type = 'calendar_node' as const
static override props = calendarNodeShapeProps
static override migrations = calendarNodeShapeMigrations
getDefaultProps(): CalendarNodeShape['props'] {
return {
w: 200,
h: 200,
color: 'white',
__primarylabel__: 'Calendar',
unique_id: '',
name: '',
start_date: '',
end_date: '',
path: '',
created: '',
merged: '',
}
}
}
export class CalendarYearNodeShapeUtil extends BaseNodeShapeUtil<CalendarYearNodeShape> {
static override type = 'calendar_year_node' as const
static override props = calendarYearNodeShapeProps
static override migrations = yearNodeShapeMigrations
getDefaultProps(): CalendarYearNodeShape['props'] {
return {
w: 200,
h: 200,
color: 'white',
__primarylabel__: 'Calendar Year',
unique_id: '',
year: '',
path: '',
created: '',
merged: ''
}
}
}
export class CalendarMonthNodeShapeUtil extends BaseNodeShapeUtil<CalendarMonthNodeShape> {
static override type = 'calendar_month_node' as const
static override props = calendarMonthNodeShapeProps
static override migrations = monthNodeShapeMigrations
getDefaultProps(): CalendarMonthNodeShape['props'] {
return {
w: 200,
h: 200,
color: 'white',
__primarylabel__: 'Calendar Month',
unique_id: '',
year: '',
month: '',
month_name: '',
path: '',
created: '',
merged: '',
}
}
}
export class CalendarWeekNodeShapeUtil extends BaseNodeShapeUtil<CalendarWeekNodeShape> {
static override type = 'calendar_week_node' as const
static override props = calendarWeekNodeShapeProps
static override migrations = weekNodeShapeMigrations
getDefaultProps(): CalendarWeekNodeShape['props'] {
return {
w: 200,
h: 200,
color: 'white',
__primarylabel__: 'Calendar Week',
unique_id: '',
start_date: '',
week_number: '',
iso_week: '',
path: '',
created: '',
merged: '',
}
}
}
export class CalendarDayNodeShapeUtil extends BaseNodeShapeUtil<CalendarDayNodeShape> {
static override type = 'calendar_day_node' as const
static override props = calendarDayNodeShapeProps
static override migrations = dayNodeShapeMigrations
getDefaultProps(): CalendarDayNodeShape['props'] {
return {
w: 200,
h: 200,
color: 'white',
__primarylabel__: 'Calendar Day',
unique_id: '',
date: '',
day_of_week: '',
iso_day: '',
path: '',
created: '',
merged: '',
}
}
}
export class CalendarTimeChunkNodeShapeUtil extends BaseNodeShapeUtil<CalendarTimeChunkNodeShape> {
static override type = 'calendar_time_chunk_node' as const
static override props = calendarTimeChunkNodeShapeProps
static override migrations = timeChunkNodeShapeMigrations
getDefaultProps(): CalendarTimeChunkNodeShape['props'] {
return {
w: 200,
h: 200,
color: 'white',
__primarylabel__: 'Calendar Time Chunk',
unique_id: '',
start_time: '',
end_time: '',
path: '',
created: '',
merged: '',
}
}
}
// School Nodes
export class SubjectClassNodeShapeUtil extends BaseNodeShapeUtil<SubjectClassNodeShape> {
static override type = 'subject_class_node' as const
static override props = subjectClassNodeShapeProps
static override migrations = subjectClassNodeShapeMigrations
getDefaultProps(): SubjectClassNodeShape['props'] {
return {
w: 200,
h: 200,
color: 'white',
__primarylabel__: 'Subject Class',
unique_id: '',
subject_class_code: '',
year_group: '',
subject: '',
subject_code: '',
path: '',
created: '',
merged: '',
}
}
}
export class SchoolNodeShapeUtil extends BaseNodeShapeUtil<SchoolNodeShape> {
static override type = 'school_node' as const
static override props = schoolNodeShapeProps
static override migrations = schoolNodeShapeMigrations
getDefaultProps(): SchoolNodeShape['props'] {
return {
w: 200,
h: 200,
color: 'white',
__primarylabel__: 'School',
unique_id: '',
school_uuid: '',
school_name: '',
school_website: '',
path: '',
created: '',
merged: '',
}
}
}
export class DepartmentNodeShapeUtil extends BaseNodeShapeUtil<DepartmentNodeShape> {
static override type = 'department_node' as const
static override props = departmentNodeShapeProps
static override migrations = departmentNodeShapeMigrations
getDefaultProps(): DepartmentNodeShape['props'] {
return {
w: 200,
h: 200,
color: 'white',
__primarylabel__: 'Department',
unique_id: '',
department_name: '',
path: '',
created: '',
merged: '',
}
}
}
export class RoomNodeShapeUtil extends BaseNodeShapeUtil<RoomNodeShape> {
static override type = 'room_node' as const
static override props = roomNodeShapeProps
static override migrations = roomNodeShapeMigrations
getDefaultProps(): RoomNodeShape['props'] {
return {
w: 200,
h: 200,
color: 'white',
__primarylabel__: 'Room',
unique_id: '',
room_name: '',
room_code: '',
path: '',
created: '',
merged: '',
}
}
}
// Curriculum Nodes
export class PastoralStructureNodeShapeUtil extends BaseNodeShapeUtil<PastoralStructureNodeShape> {
static override type = 'pastoral_structure_node' as const
static override props = pastoralStructureNodeShapeProps
static override migrations = pastoralStructureNodeShapeMigrations
getDefaultProps(): PastoralStructureNodeShape['props'] {
return {
w: 200,
h: 130,
color: 'white',
__primarylabel__: 'Pastoral Structure',
unique_id: '',
path: '',
created: '',
merged: '',
}
}
}
export class YearGroupNodeShapeUtil extends BaseNodeShapeUtil<YearGroupNodeShape> {
static override type = 'year_group_node' as const
static override props = yearGroupNodeShapeProps
static override migrations = yearGroupNodeShapeMigrations
getDefaultProps(): YearGroupNodeShape['props'] {
return {
w: 200,
h: 150,
color: 'white',
__primarylabel__: 'Year Group',
unique_id: '',
year_group: '',
year_group_name: '',
path: '',
created: '',
merged: '',
}
}
}
export class CurriculumStructureNodeShapeUtil extends BaseNodeShapeUtil<CurriculumStructureNodeShape> {
static override type = 'curriculum_structure_node' as const
static override props = curriculumStructureNodeShapeProps
static override migrations = curriculumStructureNodeShapeMigrations
getDefaultProps(): CurriculumStructureNodeShape['props'] {
return {
w: 200,
h: 130,
color: 'white',
__primarylabel__: 'Curriculum Structure',
unique_id: '',
path: '',
created: '',
merged: '',
}
}
}
export class KeyStageNodeShapeUtil extends BaseNodeShapeUtil<KeyStageNodeShape> {
static override type = 'key_stage_node' as const
static override props = keyStageNodeShapeProps
static override migrations = keyStageNodeShapeMigrations
getDefaultProps(): KeyStageNodeShape['props'] {
return {
w: 200,
h: 150,
color: 'white',
__primarylabel__: 'Key Stage',
unique_id: '',
key_stage_name: '',
key_stage: '',
path: '',
created: '',
merged: '',
}
}
}
export class KeyStageSyllabusNodeShapeUtil extends BaseNodeShapeUtil<KeyStageSyllabusNodeShape> {
static override type = 'key_stage_syllabus_node' as const
static override props = keyStageSyllabusNodeShapeProps
static override migrations = keyStageSyllabusNodeShapeMigrations
getDefaultProps(): KeyStageSyllabusNodeShape['props'] {
return {
w: 200,
h: 200,
color: 'white',
__primarylabel__: 'Key Stage Syllabus',
unique_id: '',
ks_syllabus_id: '',
ks_syllabus_name: '',
ks_syllabus_key_stage: '',
ks_syllabus_subject: '',
ks_syllabus_subject_code: '',
path: '',
created: '',
merged: '',
}
}
}
export class YearGroupSyllabusNodeShapeUtil extends BaseNodeShapeUtil<YearGroupSyllabusNodeShape> {
static override type = 'year_group_syllabus_node' as const
static override props = yearGroupSyllabusNodeShapeProps
static override migrations = yearGroupSyllabusNodeShapeMigrations
getDefaultProps(): YearGroupSyllabusNodeShape['props'] {
return {
w: 200,
h: 200,
color: 'white',
__primarylabel__: 'Year Group Syllabus',
unique_id: '',
yr_syllabus_id: '',
yr_syllabus_name: '',
yr_syllabus_year_group: '',
yr_syllabus_subject: '',
yr_syllabus_subject_code: '',
path: '',
created: '',
merged: '',
}
}
}
export class SubjectNodeShapeUtil extends BaseNodeShapeUtil<SubjectNodeShape> {
static override type = 'subject_node' as const
static override props = subjectNodeShapeProps
static override migrations = subjectNodeShapeMigrations
getDefaultProps(): SubjectNodeShape['props'] {
return {
w: 200,
h: 200,
color: 'white',
__primarylabel__: 'Subject',
unique_id: '',
subject_code: '',
subject_name: '',
path: '',
created: '',
merged: '',
}
}
}
export class TopicNodeShapeUtil extends BaseNodeShapeUtil<TopicNodeShape> {
static override type = 'topic_node' as const
static override props = topicNodeShapeProps
static override migrations = topicNodeShapeMigrations
getDefaultProps(): TopicNodeShape['props'] {
return {
w: 300,
h: 400,
color: 'white',
__primarylabel__: 'Topic',
unique_id: '',
topic_id: '',
topic_title: '',
total_number_of_lessons_for_topic: '',
topic_type: '',
topic_assessment_type: '',
path: '',
created: '',
merged: '',
}
}
}
export class TopicLessonNodeShapeUtil extends BaseNodeShapeUtil<TopicLessonNodeShape> {
static override type = 'topic_lesson_node' as const
static override props = topicLessonNodeShapeProps
static override migrations = topicLessonNodeShapeMigrations
getDefaultProps(): TopicLessonNodeShape['props'] {
return {
w: 300,
h: 500,
color: 'white',
__primarylabel__: 'Topic Lesson',
unique_id: '',
topic_lesson_id: '',
topic_lesson_title: '',
topic_lesson_type: '',
topic_lesson_length: '',
topic_lesson_skills_learned: '',
topic_lesson_suggested_activities: '',
topic_lesson_weblinks: '',
path: '',
created: '',
merged: '',
}
}
}
export class LearningStatementNodeShapeUtil extends BaseNodeShapeUtil<LearningStatementNodeShape> {
static override type = 'learning_statement_node' as const
static override props = learningStatementNodeShapeProps
static override migrations = learningStatementNodeShapeMigrations
getDefaultProps(): LearningStatementNodeShape['props'] {
return {
w: 180,
h: 300,
color: 'light-blue',
__primarylabel__: 'Learning Statement',
unique_id: '',
lesson_learning_statement_id: '',
lesson_learning_statement: '',
lesson_learning_statement_type: '',
path: '',
created: '',
merged: '',
}
}
}
export class ScienceLabNodeShapeUtil extends BaseNodeShapeUtil<ScienceLabNodeShape> {
static override type = 'science_lab_node' as const
static override props = scienceLabNodeShapeProps
static override migrations = scienceLabNodeShapeMigrations
getDefaultProps(): ScienceLabNodeShape['props'] {
return {
w: 300,
h: 400,
color: 'white',
__primarylabel__: 'Science Lab',
unique_id: '',
science_lab_id: '',
science_lab_title: '',
science_lab_summary: '',
science_lab_requirements: '',
science_lab_procedure: '',
science_lab_safety: '',
science_lab_weblinks: '',
path: '',
created: '',
merged: '',
}
}
}
// School Timetable Nodes
export class SchoolTimetableNodeShapeUtil extends BaseNodeShapeUtil<SchoolTimetableNodeShape> {
static override type = 'school_timetable_node' as const
static override props = schoolTimetableNodeShapeProps
static override migrations = schoolTimetableNodeShapeMigrations
getDefaultProps(): SchoolTimetableNodeShape['props'] {
return {
w: 200,
h: 200,
color: 'white',
__primarylabel__: 'School Timetable',
unique_id: '',
start_date: '',
end_date: '',
path: '',
created: '',
merged: '',
}
}
}
export class AcademicYearNodeShapeUtil extends BaseNodeShapeUtil<AcademicYearNodeShape> {
static override type = 'academic_year_node' as const
static override props = academicYearNodeShapeProps
static override migrations = academicYearNodeShapeMigrations
getDefaultProps(): AcademicYearNodeShape['props'] {
return {
w: 200,
h: 200,
color: 'white',
__primarylabel__: 'Academic Year',
unique_id: '',
year: '',
path: '',
created: '',
merged: '',
}
}
}
export class AcademicTermNodeShapeUtil extends BaseNodeShapeUtil<AcademicTermNodeShape> {
static override type = 'academic_term_node' as const
static override props = academicTermNodeShapeProps
static override migrations = academicTermNodeShapeMigrations
getDefaultProps(): AcademicTermNodeShape['props'] {
return {
w: 300,
h: 200,
color: 'white',
__primarylabel__: 'Academic Term',
unique_id: '',
term_name: '',
term_number: '',
start_date: '',
end_date: '',
path: '',
created: '',
merged: '',
}
}
}
export class AcademicWeekNodeShapeUtil extends BaseNodeShapeUtil<AcademicWeekNodeShape> {
static override type = 'academic_week_node' as const
static override props = academicWeekNodeShapeProps
static override migrations = academicWeekNodeShapeMigrations
getDefaultProps(): AcademicWeekNodeShape['props'] {
return {
w: 300,
h: 200,
color: 'white',
__primarylabel__: 'Academic Week',
unique_id: '',
start_date: '',
week_type: '',
academic_week_number: '',
path: '',
created: '',
merged: '',
}
}
}
export class AcademicDayNodeShapeUtil extends BaseNodeShapeUtil<AcademicDayNodeShape> {
static override type = 'academic_day_node' as const
static override props = academicDayNodeShapeProps
static override migrations = academicDayNodeShapeMigrations
getDefaultProps(): AcademicDayNodeShape['props'] {
return {
w: 200,
h: 200,
color: 'white',
__primarylabel__: 'Academic Day',
unique_id: '',
academic_day: '',
date: '',
day_of_week: '',
day_type: '',
path: '',
created: '',
merged: '',
}
}
}
export class AcademicPeriodNodeShapeUtil extends BaseNodeShapeUtil<AcademicPeriodNodeShape> {
static override type = 'academic_period_node' as const
static override props = academicPeriodNodeShapeProps
static override migrations = academicPeriodNodeShapeMigrations
getDefaultProps(): AcademicPeriodNodeShape['props'] {
return {
w: 200,
h: 300,
color: 'white',
__primarylabel__: 'Academic Period',
unique_id: '',
name: '',
date: '',
start_time: '',
end_time: '',
period_code: '',
path: '',
created: '',
merged: '',
}
}
}
export class RegistrationPeriodNodeShapeUtil extends BaseNodeShapeUtil<RegistrationPeriodNodeShape> {
static override type = 'registration_period_node' as const
static override props = registrationPeriodNodeShapeProps
static override migrations = registrationPeriodNodeShapeMigrations
getDefaultProps(): RegistrationPeriodNodeShape['props'] {
return {
w: 200,
h: 200,
color: 'white',
__primarylabel__: 'Registration Period',
unique_id: '',
name: '',
date: '',
start_time: '',
end_time: '',
period_code: '',
path: '',
created: '',
merged: '',
}
}
}
// Teacher Timetable Nodes
export class TeacherTimetableNodeShapeUtil extends BaseNodeShapeUtil<TeacherTimetableNodeShape> {
static override type = 'teacher_timetable_node' as const
static override props = teacherTimetableNodeShapeProps
static override migrations = teacherTimetableNodeShapeMigrations
getDefaultProps(): TeacherTimetableNodeShape['props'] {
return {
w: 200,
h: 130,
color: 'white',
__primarylabel__: 'Teacher Timetable',
unique_id: '',
path: '',
created: '',
merged: '',
}
}
}
export class TimetableLessonNodeShapeUtil extends BaseNodeShapeUtil<TimetableLessonNodeShape> {
static override type = 'timetable_lesson_node' as const
static override props = timetableLessonNodeShapeProps
static override migrations = timetableLessonNodeShapeMigrations
override isAspectRatioLocked = (_shape: TimetableLessonNodeShape) => true
override canResize = (_shape: TimetableLessonNodeShape) => true
getDefaultProps(): TimetableLessonNodeShape['props'] {
return {
w: 200,
h: 250,
color: 'white',
__primarylabel__: 'Timetable Lesson',
unique_id: '',
subject_class: '',
date: '',
start_time: '',
end_time: '',
period_code: '',
path: '',
created: '',
merged: '',
}
}
}
export class PlannedLessonNodeShapeUtil extends BaseNodeShapeUtil<PlannedLessonNodeShape> {
static override type = 'planned_lesson_node' as const
static override props = plannedLessonNodeShapeProps
static override migrations = plannedLessonNodeShapeMigrations
getDefaultProps(): PlannedLessonNodeShape['props'] {
return {
w: 200,
h: 250,
color: 'white',
__primarylabel__: 'Planned Lesson',
unique_id: '',
date: '',
start_time: '',
end_time: '',
period_code: '',
subject_class: '',
year_group: '',
subject: '',
teacher_code: '',
planning_status: '',
topic_code: '',
topic_name: '',
lesson_code: '',
lesson_name: '',
learning_statement_codes: '',
learning_statements: '',
learning_resource_codes: '',
learning_resources: '',
path: '',
created: '',
merged: '',
}
}
}
// Relationships
export class GeneralRelationshipShapeUtil extends BaseRelationshipShapeUtil<GeneralRelationshipShape> {
static override type = 'general_relationship' as const
static override props = generalRelationshipShapeProps
static override migrations = generalRelationshipShapeMigrations
getDefaultProps(): GeneralRelationshipShape['props'] {
return {
w: 200,
h: 250,
color: 'black',
__relationshiptype__: '',
source: '',
target: '',
}
}
}
export const allShapeUtils = [
DeveloperNodeShapeUtil,
TeacherNodeShapeUtil,
StudentNodeShapeUtil,
UserNodeShapeUtil,
TeacherTimetableNodeShapeUtil,
TimetableLessonNodeShapeUtil,
PlannedLessonNodeShapeUtil,
SchoolNodeShapeUtil,
CalendarNodeShapeUtil,
CalendarYearNodeShapeUtil,
CalendarMonthNodeShapeUtil,
CalendarWeekNodeShapeUtil,
CalendarDayNodeShapeUtil,
CalendarTimeChunkNodeShapeUtil,
ScienceLabNodeShapeUtil,
KeyStageSyllabusNodeShapeUtil,
YearGroupSyllabusNodeShapeUtil,
CurriculumStructureNodeShapeUtil,
TopicNodeShapeUtil,
TopicLessonNodeShapeUtil,
LearningStatementNodeShapeUtil,
SchoolTimetableNodeShapeUtil,
AcademicYearNodeShapeUtil,
AcademicTermNodeShapeUtil,
AcademicWeekNodeShapeUtil,
AcademicDayNodeShapeUtil,
AcademicPeriodNodeShapeUtil,
RegistrationPeriodNodeShapeUtil,
DepartmentNodeShapeUtil,
RoomNodeShapeUtil,
PastoralStructureNodeShapeUtil,
YearGroupNodeShapeUtil,
KeyStageNodeShapeUtil
];

View File

@ -0,0 +1,102 @@
import dagre from '@dagrejs/dagre';
import { createShapeId, Editor } from 'tldraw';
const graphState = {
g: new dagre.graphlib.Graph(),
nodeData: new Map<string, any>(),
editor: null as Editor | null,
initGraph: () => {
graphState.g.setGraph({});
graphState.g.setDefaultEdgeLabel(() => ({}));
},
updateNodesWithDagre: () => {
dagre.layout(graphState.g);
// Update positions in nodeData after layout
graphState.g.nodes().forEach((id) => {
const node = graphState.g.node(id);
if (graphState.nodeData.has(id)) {
const fullNode = graphState.nodeData.get(id);
fullNode.x = node.x;
fullNode.y = node.y;
graphState.nodeData.set(id, fullNode);
}
});
},
updateShapesWithDagre: () => {
console.log("Updating shapes with dagre...");
if (!graphState.editor) {
console.error("Editor is not set. Call setEditor before updating shapes.");
return;
}
console.log("Updating nodes with dagre...");
graphState.updateNodesWithDagre();
console.log("Nodes updated with dagre...");
console.log("Updating set of shapes with dagre...");
graphState.nodeData.forEach((shape, id) => {
console.log("Updating shape with dagre:", shape, id);
const node = graphState.g.node(id);
console.log("Node without w and h:", node);
const nodeWithWidthAndHeight = {
...node,
width: shape.w,
height: shape.h
}
console.log("Node with w and h:", nodeWithWidthAndHeight);
if (nodeWithWidthAndHeight) {
console.log("Updating shape:", shape);
graphState.editor!.updateShape({
id: createShapeId(node.label),
type: shape.type,
x: nodeWithWidthAndHeight.x - nodeWithWidthAndHeight.width / 2,
y: nodeWithWidthAndHeight.y - nodeWithWidthAndHeight.height / 2,
});
}
});
},
addNode: (shape: any) => {
console.log("Adding shape to graphState:", shape);
const id = shape.props.unique_id;
console.log("Adding node to graphState:", id);
graphState.g.setNode(id, {
label: id,
width: shape.props.w,
height: shape.props.h
});
graphState.nodeData.set(id, shape);
},
addEdge: (source: string, target: string) => {
graphState.g.setEdge(source, target);
},
getNode: (id: string) => {
return graphState.nodeData.get(id);
},
getAllNodes: () => {
return Array.from(graphState.nodeData.values()).filter(item => {
// Check if the item has a type property and it's not an edge type
return item.type && !item.type.includes('relationship');
});
},
getEdges: () => {
return graphState.g.edges();
},
setEditor: (editor: Editor) => {
graphState.editor = editor;
}
};
graphState.initGraph();
export default graphState;

View File

@ -0,0 +1,689 @@
import React from 'react';
import { AllNodeShapes } from './graph-shape-types';
interface NodeComponentProps<T extends AllNodeShapes = AllNodeShapes> {
shape: T;
theme: any;
}
interface BaseNodeProps {
__primarylabel__: string;
unique_id: string;
}
interface TeacherNodeProps extends BaseNodeProps {
teacher_name_formal: string;
teacher_code: string;
teacher_email: string;
worker_db_name: string;
}
interface StudentNodeProps extends BaseNodeProps {
student_name_formal: string;
student_code: string;
student_email: string;
worker_db_name: string;
}
interface UserNodeProps extends BaseNodeProps {
user_name: string;
user_email: string;
user_type: string;
}
interface CalendarNodeProps extends BaseNodeProps {
name: string;
start_date: string;
end_date: string;
}
interface CalendarNodeProps extends BaseNodeProps {
name: string;
start_date: string;
end_date: string;
}
interface CalendarYearNodeProps extends BaseNodeProps {
year: string;
}
interface CalendarMonthNodeProps extends BaseNodeProps {
month_name: string;
year: string;
}
interface CalendarWeekNodeProps extends BaseNodeProps {
start_date: string;
iso_week: string;
}
interface CalendarDayNodeProps extends BaseNodeProps {
day_of_week: string;
date: string;
}
interface CalendarTimeChunkNodeProps extends BaseNodeProps {
start_time: string;
end_time: string;
}
interface CalendarTimeChunkNodeProps extends BaseNodeProps {
start_time: string;
end_time: string;
}
interface SchoolNodeProps extends BaseNodeProps {
school_name: string;
school_website: string;
}
interface DepartmentNodeProps extends BaseNodeProps {
department_name: string;
}
interface RoomNodeProps extends BaseNodeProps {
room_name: string;
room_code: string;
}
interface SubjectClassNodeProps extends BaseNodeProps {
subject_class_code: string;
year_group: string;
subject: string;
}
interface PastoralStructureNodeProps extends BaseNodeProps {
}
interface YearGroupNodeProps extends BaseNodeProps {
year_group: string;
}
interface CurriculumStructureNodeProps extends BaseNodeProps {
}
interface KeyStageNodeProps extends BaseNodeProps {
key_stage: string;
}
interface KeyStageSyllabusNodeProps extends BaseNodeProps {
ks_syllabus_id: string;
ks_syllabus_subject: string;
}
interface YearGroupSyllabusNodeProps extends BaseNodeProps {
yr_syllabus_id: string;
yr_syllabus_subject: string;
}
interface SubjectNodeProps extends BaseNodeProps {
subject_name: string;
subject_code: string;
}
interface TopicNodeProps extends BaseNodeProps {
topic_title: string;
topic_id: string;
total_number_of_lessons_for_topic: string;
topic_type: string;
topic_assessment_type: string;
}
interface TopicLessonNodeProps extends BaseNodeProps {
topic_lesson_title: string;
topic_lesson_id: string;
topic_lesson_type: string;
topic_lesson_length: string;
topic_lesson_suggested_activities: string;
topic_lesson_skills_learned: string;
topic_lesson_weblinks: string;
}
interface LearningStatementNodeProps extends BaseNodeProps {
lesson_learning_statement: string;
lesson_learning_statement_id: string;
lesson_learning_statement_type: string;
}
interface ScienceLabNodeProps extends BaseNodeProps {
science_lab_title: string;
science_lab_id: string;
science_lab_summary: string;
science_lab_requirements: string;
science_lab_procedure: string;
science_lab_safety: string;
science_lab_weblinks: string;
}
interface SchoolTimetableNodeProps extends BaseNodeProps {
start_date: string;
end_date: string;
}
interface AcademicYearNodeProps extends BaseNodeProps {
year: string;
}
interface AcademicTermNodeProps extends BaseNodeProps {
term_name: string;
term_number: string;
start_date: string;
end_date: string;
}
interface AcademicWeekNodeProps extends BaseNodeProps {
start_date: string;
week_type: string;
academic_week_number: string;
}
interface AcademicDayNodeProps extends BaseNodeProps {
day_of_week: string;
day_type: string;
date: string;
}
interface AcademicPeriodNodeProps extends BaseNodeProps {
name: string;
date: string;
start_time: string;
end_time: string;
period_code: string;
}
interface RegistrationPeriodNodeProps extends BaseNodeProps {
name: string;
date: string;
start_time: string;
end_time: string;
period_code: string;
}
interface TeacherTimetableNodeProps extends BaseNodeProps {
}
interface TimetableLessonNodeProps extends BaseNodeProps {
subject_class: string;
date: string;
period_code: string;
}
interface PlannedLessonNodeProps extends BaseNodeProps {
subject_class: string;
date: string;
period_code: string;
planning_status: string;
teacher_code: string;
year_group: string;
subject: string;
}
const DefaultNodeComponent: React.FC<NodeComponentProps> = ({ shape, theme }) => (
<>
<div className="w-full flex justify-center" style={{ marginBottom: '5px' }}>
<div
className="w-8 h-8 rounded-full bg-blue-500 flex items-center justify-center"
style={{ color: 'white', fontWeight: 'bold' }}
>
{(shape.props.__primarylabel__).toUpperCase()}
</div>
</div>
</>
);
// Users
const UserNodeComponent: React.FC<NodeComponentProps> = ({ shape, theme }) => {
const props = shape.props as UserNodeProps;
return (
<>
<DefaultNodeComponent shape={shape} theme={theme} />
<div>User Name: {props.user_name}</div>
<div>User Email: {props.user_email}</div>
</>
);
};
const DeveloperNodeComponent: React.FC<NodeComponentProps> = ({ shape, theme }) => {
const props = shape.props as UserNodeProps;
return (
<>
<DefaultNodeComponent shape={shape} theme={theme} />
<div>User Name: {props.user_name}</div>
<div>Use Email: {props.user_email}</div>
</>
);
};
const TeacherNodeComponent: React.FC<NodeComponentProps> = ({ shape, theme }) => {
const props = shape.props as TeacherNodeProps;
return (
<>
<DefaultNodeComponent shape={shape} theme={theme} />
<div>Teacher Name: {props.teacher_name_formal}</div>
<div>Teacher Code: {props.teacher_code}</div>
<div>Email: {props.teacher_email}</div>
</>
);
};
const StudentNodeComponent: React.FC<NodeComponentProps> = ({ shape, theme }) => {
const props = shape.props as StudentNodeProps;
return (
<>
<DefaultNodeComponent shape={shape} theme={theme} />
<div>Student Name: {props.student_name_formal}</div>
<div>Student Code: {props.student_code}</div>
<div>Email: {props.student_email}</div>
</>
);
};
// Calendar
const CalendarNodeComponent: React.FC<NodeComponentProps> = ({ shape, theme }) => {
const props = shape.props as CalendarNodeProps;
return (
<>
<DefaultNodeComponent shape={shape} theme={theme} />
<div>Calendar Name: {props.name}</div>
<div>Start Date: {props.start_date}</div>
<div>End Date: {props.end_date}</div>
</>
);
};
const CalendarYearNodeComponent: React.FC<NodeComponentProps> = ({ shape, theme }) => {
const props = shape.props as CalendarYearNodeProps;
return (
<>
<DefaultNodeComponent shape={shape} theme={theme} />
<div>Year: {props.year}</div>
</>
);
};
const CalendarMonthNodeComponent: React.FC<NodeComponentProps> = ({ shape, theme }) => {
const props = shape.props as CalendarMonthNodeProps;
return (
<>
<DefaultNodeComponent shape={shape} theme={theme} />
<div>Month: {props.month_name}</div>
<div>Year: {props.year}</div>
</>
);
};
const CalendarWeekNodeComponent: React.FC<NodeComponentProps> = ({ shape, theme }) => {
const props = shape.props as CalendarWeekNodeProps;
return (
<>
<DefaultNodeComponent shape={shape} theme={theme} />
<div>Week Start Date: {props.start_date}</div>
<div>ISO Week: {props.iso_week}</div>
</>
);
};
const CalendarDayNodeComponent: React.FC<NodeComponentProps> = ({ shape, theme }) => {
const props = shape.props as CalendarDayNodeProps;
return (
<>
<DefaultNodeComponent shape={shape} theme={theme} />
<div>Day of Week: {props.day_of_week}</div>
<div>Date: {props.date}</div>
</>
);
};
const CalendarTimeChunkNodeComponent: React.FC<NodeComponentProps> = ({ shape, theme }) => {
const props = shape.props as CalendarTimeChunkNodeProps;
return (
<>
<DefaultNodeComponent shape={shape} theme={theme} />
<div>Start Time: {props.start_time}</div>
<div>End Time: {props.end_time}</div>
</>
);
};
// Schools
const SchoolNodeComponent: React.FC<NodeComponentProps> = ({ shape, theme }) => {
const props = shape.props as SchoolNodeProps;
return (
<>
<DefaultNodeComponent shape={shape} theme={theme} />
<div>School Name: {props.school_name}</div>
<div>School Website: {props.school_website}</div>
</>
);
};
const DepartmentNodeComponent: React.FC<NodeComponentProps> = ({ shape, theme }) => {
const props = shape.props as DepartmentNodeProps;
return (
<>
<DefaultNodeComponent shape={shape} theme={theme} />
<div>Department Name: {props.department_name}</div>
</>
);
};
const RoomNodeComponent: React.FC<NodeComponentProps> = ({ shape, theme }) => {
const props = shape.props as RoomNodeProps;
return (
<>
<DefaultNodeComponent shape={shape} theme={theme} />
<div>Room Name: {props.room_name}</div>
<div>Room Code: {props.room_code}</div>
</>
);
};
const SubjectClassNodeComponent: React.FC<NodeComponentProps> = ({ shape, theme }) => {
const props = shape.props as SubjectClassNodeProps;
return (
<>
<DefaultNodeComponent shape={shape} theme={theme} />
<div>Subject Class: {props.subject_class_code}</div>
<div>Year Group: {props.year_group}</div>
<div>Subject: {props.subject}</div>
</>
);
};
// Curriculum
const PastoralStructureNodeComponent: React.FC<NodeComponentProps> = ({ shape, theme }) => {
const props = shape.props as PastoralStructureNodeProps;
return (
<>
<DefaultNodeComponent shape={shape} theme={theme} />
</>
);
};
const YearGroupNodeComponent: React.FC<NodeComponentProps> = ({ shape, theme }) => {
const props = shape.props as YearGroupNodeProps;
return (
<>
<DefaultNodeComponent shape={shape} theme={theme} />
<div>Year Group: {props.year_group}</div>
</>
);
};
const CurriculumStructureNodeComponent: React.FC<NodeComponentProps> = ({ shape, theme }) => {
const props = shape.props as CurriculumStructureNodeProps;
return (
<>
<DefaultNodeComponent shape={shape} theme={theme} />
</>
);
};
const KeyStageNodeComponent: React.FC<NodeComponentProps> = ({ shape, theme }) => {
const props = shape.props as KeyStageNodeProps;
return (
<>
<DefaultNodeComponent shape={shape} theme={theme} />
<div>Key Stage: {props.key_stage}</div>
</>
);
};
const KeyStageSyllabusNodeComponent: React.FC<NodeComponentProps> = ({ shape, theme }) => {
const props = shape.props as KeyStageSyllabusNodeProps;
return (
<>
<DefaultNodeComponent shape={shape} theme={theme} />
<div>Syllabus ID: {props.ks_syllabus_id}</div>
<div>Subject: {props.ks_syllabus_subject}</div>
</>
);
};
const YearGroupSyllabusNodeComponent: React.FC<NodeComponentProps> = ({ shape, theme }) => {
const props = shape.props as YearGroupSyllabusNodeProps;
return (
<>
<DefaultNodeComponent shape={shape} theme={theme} />
<div>Syllabus ID: {props.yr_syllabus_id}</div>
<div>Subject: {props.yr_syllabus_subject}</div>
</>
);
};
const SubjectNodeComponent: React.FC<NodeComponentProps> = ({ shape, theme }) => {
const props = shape.props as SubjectNodeProps;
return (
<>
<DefaultNodeComponent shape={shape} theme={theme} />
<div>Subject: {props.subject_name}</div>
<div>Code: {props.subject_code}</div>
</>
);
};
const TopicNodeComponent: React.FC<NodeComponentProps> = ({ shape, theme }) => {
const props = shape.props as TopicNodeProps;
return (
<>
<DefaultNodeComponent shape={shape} theme={theme} />
<div>Title: {props.topic_title}</div>
<div>ID: {props.topic_id}</div>
<div>Lessons: {props.total_number_of_lessons_for_topic}</div>
<div>Type: {props.topic_type}</div>
<div>Assessment Type: {props.topic_assessment_type}</div>
</>
);
};
const TopicLessonNodeComponent: React.FC<NodeComponentProps> = ({ shape, theme }) => {
const props = shape.props as TopicLessonNodeProps;
return (
<>
<DefaultNodeComponent shape={shape} theme={theme} />
<div>Title: {props.topic_lesson_title}</div>
<div>ID: {props.topic_lesson_id}</div>
<div>Type: {props.topic_lesson_type}</div>
<div>Length: {props.topic_lesson_length}</div>
<div>Suggested Activities: {props.topic_lesson_suggested_activities}</div>
<div>Skills Learned: {props.topic_lesson_skills_learned}</div>
<div>Web Links: {props.topic_lesson_weblinks}</div>
</>
);
};
const LearningStatementNodeComponent: React.FC<NodeComponentProps> = ({ shape, theme }) => {
const props = shape.props as LearningStatementNodeProps;
return (
<>
<DefaultNodeComponent shape={shape} theme={theme} />
<div>Learning Statement: {props.lesson_learning_statement}</div>
<div>ID: {props.lesson_learning_statement_id}</div>
<div>Type: {props.lesson_learning_statement_type}</div>
</>
);
};
const ScienceLabNodeComponent: React.FC<NodeComponentProps> = ({ shape, theme }) => {
const props = shape.props as ScienceLabNodeProps;
return (
<>
<DefaultNodeComponent shape={shape} theme={theme} />
<div>Title: {props.science_lab_title}</div>
<div>ID: {props.science_lab_id}</div>
<div>Summary: {props.science_lab_summary}</div>
<div>Requirements: {props.science_lab_requirements}</div>
<div>Procedure: {props.science_lab_procedure}</div>
<div>Safety: {props.science_lab_safety}</div>
<div>Web Links: {props.science_lab_weblinks}</div>
</>
);
};
// School Timetable
const SchoolTimetableNodeComponent: React.FC<NodeComponentProps> = ({ shape, theme }) => {
const props = shape.props as SchoolTimetableNodeProps;
return (
<>
<DefaultNodeComponent shape={shape} theme={theme} />
<div>Start Date: {props.start_date}</div>
<div>End Date: {props.end_date}</div>
</>
);
};
const AcademicYearNodeComponent: React.FC<NodeComponentProps> = ({ shape, theme }) => {
const props = shape.props as AcademicYearNodeProps;
return (
<>
<DefaultNodeComponent shape={shape} theme={theme} />
<div>Year: {props.year}</div>
</>
);
};
const AcademicTermNodeComponent: React.FC<NodeComponentProps> = ({ shape, theme }) => {
const props = shape.props as AcademicTermNodeProps;
return (
<>
<DefaultNodeComponent shape={shape} theme={theme} />
<div>Term Name: {props.term_name}</div>
<div>Term Number: {props.term_number}</div>
<div>Start Date: {props.start_date}</div>
<div>End Date: {props.end_date}</div>
</>
);
};
const AcademicWeekNodeComponent: React.FC<NodeComponentProps> = ({ shape, theme }) => {
const props = shape.props as AcademicWeekNodeProps;
return (
<>
<DefaultNodeComponent shape={shape} theme={theme} />
<div>Start Date: {props.start_date}</div>
<div>Week Type: {props.week_type}</div>
<div>Academic Week Number: {props.academic_week_number}</div>
</>
);
};
const AcademicDayNodeComponent: React.FC<NodeComponentProps> = ({ shape, theme }) => {
const props = shape.props as AcademicDayNodeProps;
return (
<>
<DefaultNodeComponent shape={shape} theme={theme} />
<div>Day of Week: {props.day_of_week}</div>
<div>Day Type: {props.day_type}</div>
<div>Date: {props.date}</div>
</>
);
};
const AcademicPeriodNodeComponent: React.FC<NodeComponentProps> = ({ shape, theme }) => {
const props = shape.props as AcademicPeriodNodeProps;
return (
<>
<DefaultNodeComponent shape={shape} theme={theme} />
<div>Name: {props.name}</div>
<div>Date: {props.date}</div>
<div>Start Time: {props.start_time}</div>
<div>End Time: {props.end_time}</div>
<div>Period Code: {props.period_code}</div>
</>
);
};
const RegistrationPeriodNodeComponent: React.FC<NodeComponentProps> = ({ shape, theme }) => {
const props = shape.props as RegistrationPeriodNodeProps;
return (
<>
<DefaultNodeComponent shape={shape} theme={theme} />
<div>Name: {props.name}</div>
<div>Date: {props.date}</div>
<div>Start Time: {props.start_time}</div>
<div>End Time: {props.end_time}</div>
<div>Period Code: {props.period_code}</div>
</>
);
};
// Teacher Timetable
const TeacherTimetableNodeComponent: React.FC<NodeComponentProps> = ({ shape, theme }) => {
const props = shape.props as TeacherTimetableNodeProps;
return (
<>
<DefaultNodeComponent shape={shape} theme={theme} />
</>
);
};
const TimetableLessonNodeComponent: React.FC<NodeComponentProps> = ({ shape, theme }) => {
const props = shape.props as TimetableLessonNodeProps;
return (
<>
<DefaultNodeComponent shape={shape} theme={theme} />
<div>Subject Class: {props.subject_class}</div>
<div>Date: {props.date}</div>
<div>Period Code: {props.period_code}</div>
</>
);
};
const PlannedLessonNodeComponent: React.FC<NodeComponentProps> = ({ shape, theme }) => {
const props = shape.props as PlannedLessonNodeProps;
return (
<>
<DefaultNodeComponent shape={shape} theme={theme} />
<div>Subject Class: {props.subject_class}</div>
<div>Year Group: {props.year_group}</div>
<div>Subject: {props.subject}</div>
<div>Teacher Code: {props.teacher_code}</div>
<div>Planning Status: {props.planning_status}</div>
</>
);
};
const nodeComponents: { [key: string]: React.FC<NodeComponentProps> } = {
user_node: UserNodeComponent,
teacher_node: TeacherNodeComponent,
student_node: StudentNodeComponent,
timetable_lesson_node: TimetableLessonNodeComponent,
developer_node: DeveloperNodeComponent,
school_node: SchoolNodeComponent,
department_node: DepartmentNodeComponent,
planned_lesson_node: PlannedLessonNodeComponent,
registration_period_node: RegistrationPeriodNodeComponent,
teacher_timetable_node: TeacherTimetableNodeComponent,
academic_year_node: AcademicYearNodeComponent,
academic_term_node: AcademicTermNodeComponent,
academic_week_node: AcademicWeekNodeComponent,
academic_day_node: AcademicDayNodeComponent,
academic_period_node: AcademicPeriodNodeComponent,
key_stage_node: KeyStageNodeComponent,
key_stage_syllabus_node: KeyStageSyllabusNodeComponent,
year_group_syllabus_node: YearGroupSyllabusNodeComponent,
subject_node: SubjectNodeComponent,
topic_node: TopicNodeComponent,
topic_lesson_node: TopicLessonNodeComponent,
learning_statement_node: LearningStatementNodeComponent,
science_lab_node: ScienceLabNodeComponent,
school_timetable_node: SchoolTimetableNodeComponent,
calendar_node: CalendarNodeComponent,
calendar_year_node: CalendarYearNodeComponent,
calendar_month_node: CalendarMonthNodeComponent,
calendar_week_node: CalendarWeekNodeComponent,
calendar_day_node: CalendarDayNodeComponent,
calendar_time_chunk_node: CalendarTimeChunkNodeComponent,
year_group_node: YearGroupNodeComponent,
pastoral_structure_node: PastoralStructureNodeComponent,
curriculum_structure_node: CurriculumStructureNodeComponent,
room_node: RoomNodeComponent,
subject_class_node: SubjectClassNodeComponent,
};
export const getNodeComponent = (shape: AllNodeShapes, theme: any) => {
const Component = nodeComponents[shape.type] || DefaultNodeComponent;
return <Component shape={shape} theme={theme} />;
};

225
src/utils/userContext.tsx Normal file
View File

@ -0,0 +1,225 @@
import { ReactNode, createContext, useContext, useState, useEffect } from 'react';
import { UserNodeInterface } from '../types/graph_node_types';
import { TLUser } from 'tldraw';
export const getFromLocalStorage = (key: string) => {
const item = localStorage.getItem(key);
if (item === null || item === 'undefined') return null;
try {
return JSON.parse(item);
} catch (error) {
console.error(`Error parsing JSON for key ${key}:`, error);
return null;
}
};
export const setToLocalStorage = (key: string, value: any) => {
localStorage.setItem(key, JSON.stringify(value));
};
export const removeFromLocalStorage = (key: string) => {
localStorage.removeItem(key);
};
export const setStateAndStorage = (setter: React.Dispatch<React.SetStateAction<any>>, key: string, value: any) => {
setter(value);
setToLocalStorage(key, value);
};
const AuthContext = createContext({
userRole: null as string | null,
firebaseIdToken: null as string | null,
msAccessToken: null as string | null,
neo4jDbName: null as string | null,
userNode: null as UserNodeInterface | null,
tldrawUserFilePath: null as string | null,
oneNoteNotebook: null as any,
isLoading: false,
error: null as string | null,
logout: async () => {},
getIdToken: async () => Promise.resolve('') as Promise<string>,
});
export const AuthProvider = ({ children }: { children: ReactNode }) => {
const [user, setUser] = useState<TLUser | null>(getFromLocalStorage('user'));
const [isLoading, setIsLoading] = useState<boolean>(true);
const [userRole, setUserRole] = useState<string | null>(getFromLocalStorage('userRole'));
const [firebaseIdToken, setToken] = useState<string | null>(getFromLocalStorage('firebaseIdToken'));
const [msAccessToken, setMsToken] = useState<string | null>(getFromLocalStorage('msAccessToken'));
const [neo4jDbName, setNeo4jDbName] = useState<string | null>(getFromLocalStorage('neo4jDbName'));
const [userNode, setUserNode] = useState<UserNodeInterface | null>(getFromLocalStorage('userNode'));
const [tldrawUserFilePath, setTldrawUserFilePath] = useState<string | null>(getFromLocalStorage('tldrawUserFilePath'));
const [oneNoteNotebook, setOneNoteNotebook] = useState<any>(getFromLocalStorage('oneNoteNotebook'));
const [error, setError] = useState<string | null>(null);
useEffect(() => {
const unsubscribe = auth.onAuthStateChanged(async (user) => {
setIsLoading(true);
try {
if (user) {
console.log("AuthProvider - useEffect - User state changed:", user);
const storedUserRole = getFromLocalStorage('userRole');
const storedFirebaseIdToken = await user.getIdToken();
const storedMsAccessToken = getFromLocalStorage('msAccessToken');
const storedNeo4jDbName = getFromLocalStorage('neo4jDbName');
const storedUserNode = getFromLocalStorage('userNode');
const storedOneNoteNotebook = getFromLocalStorage('oneNoteNotebook');
setStateAndStorage(setUser, 'user', user);
setStateAndStorage(setUserRole, 'userRole', storedUserRole);
setStateAndStorage(setToken, 'firebaseIdToken', storedFirebaseIdToken);
setStateAndStorage(setMsToken, 'msAccessToken', storedMsAccessToken);
setStateAndStorage(setNeo4jDbName, 'neo4jDbName', storedNeo4jDbName);
setStateAndStorage(setUserNode, 'userNode', storedUserNode);
setStateAndStorage(setTldrawUserFilePath, 'tldrawUserFilePath', storedUserNode?.path);
setStateAndStorage(setOneNoteNotebook, 'oneNoteNotebook', storedOneNoteNotebook);
console.log("AuthProvider - useEffect - Updated state:", { user, userRole: storedUserRole });
} else {
setStateAndStorage(setUser, 'user', null);
setStateAndStorage(setUserRole, 'userRole', null);
setStateAndStorage(setToken, 'firebaseIdToken', null);
setStateAndStorage(setMsToken, 'msAccessToken', null);
setStateAndStorage(setNeo4jDbName, 'neo4jDbName', null);
setStateAndStorage(setUserNode, 'userNode', null);
setStateAndStorage(setTldrawUserFilePath, 'tldrawUserFilePath', null);
setStateAndStorage(setOneNoteNotebook, 'oneNoteNotebook', null);
removeFromLocalStorage('userRole');
removeFromLocalStorage('msAccessToken');
removeFromLocalStorage('neo4jDbName');
removeFromLocalStorage('userNode');
removeFromLocalStorage('oneNoteNotebook');
}
} catch (error) {
setError(error instanceof Error ? error.message : 'An unknown error occurred');
} finally {
setIsLoading(false);
}
});
return () => unsubscribe();
}, []);
const login = async (email: string, password: string, role: string) => {
try {
const { user, firebaseIdToken, firestoreUserDoc } = await emailLogin(email, password);
const userRole = role || firestoreUserDoc?.userRole || null;
const userNode = await fetchUserNode(user.uid);
setStateAndStorage(setUser, 'user', user);
setStateAndStorage(setUserRole, 'userRole', userRole);
setStateAndStorage(setToken, 'firebaseIdToken', firebaseIdToken);
setStateAndStorage(setUserNode, 'userNode', userNode);
setStateAndStorage(setTldrawUserFilePath, 'tldrawUserFilePath', userNode.path);
return { user, firebaseIdToken, userNode, userRole, message: 'Login successful' };
} catch (error) {
setError(error instanceof Error ? error.message : 'An unknown error occurred');
return { user: null, firebaseIdToken: null, userNode: null, userRole: null, message: 'Login failed' };
}
};
const logout = async () => {
console.log("Logging out...");
setIsLoading(true);
setError(null);
try {
await logoutUser();
setStateAndStorage(setUser, 'user', null);
setStateAndStorage(setUserRole, 'userRole', null);
setStateAndStorage(setToken, 'firebaseIdToken', null);
setStateAndStorage(setMsToken, 'msAccessToken', null);
setStateAndStorage(setNeo4jDbName, 'neo4jDbName', null);
setStateAndStorage(setUserNode, 'userNode', null);
setStateAndStorage(setTldrawUserFilePath, 'tldrawUserFilePath', null);
setStateAndStorage(setOneNoteNotebook, 'oneNoteNotebook', null);
removeFromLocalStorage('user');
removeFromLocalStorage('firebaseIdToken');
removeFromLocalStorage('msAccessToken');
removeFromLocalStorage('neo4jDbName');
removeFromLocalStorage('userNode');
removeFromLocalStorage('tldrawUserFilePath');
removeFromLocalStorage('oneNoteNotebook');
} catch (error) {
setError(error instanceof Error ? error.message : 'An unknown error occurred');
} finally {
console.log("Finished logging out");
setIsLoading(false);
}
};
const registerUser = async (email: string, password: string, username: string, userRole: string): Promise<{ user: User | null; firebaseIdToken: string | null; userNode: UserNodeInterface | null; userRole: string | null; message: string | null }> => {
try {
const { user, firebaseIdToken, neo4jDbName, userNode, tldrawUserFilePath } = await registerUserWithEmailAndPassword(email, password, username, userRole);
setStateAndStorage(setUser, 'user', user);
setStateAndStorage(setUserRole, 'userRole', userRole);
setStateAndStorage(setToken, 'firebaseIdToken', firebaseIdToken);
setStateAndStorage(setNeo4jDbName, 'neo4jDbName', neo4jDbName);
setStateAndStorage(setUserNode, 'userNode', userNode);
setStateAndStorage(setTldrawUserFilePath, 'tldrawUserFilePath', userNode.path);
return { user, firebaseIdToken, userNode, userRole, message: 'Email user registered successfully' };
} catch (error) {
return { user: null, firebaseIdToken: null, userNode: null, userRole: null, message: error instanceof Error ? error.message : 'An unknown error occurred' };
}
};
const registerOrLoginWithMicrosoft = async (role: string): Promise<{ user: User | null; firebaseIdToken: string | null; msAccessToken: string | null; userNode: UserNodeInterface | null; userRole: string | null; message: string | null; oneNoteNotebook: any }> => {
try {
console.log("registerOrLoginWithMicrosoft - Starting");
console.log(`Ready to sign in with Microsoft as ${role}`);
const { user, firebaseIdToken, msAccessToken, neo4jDbName, userNode, oneNoteNotebook, isNewUser } = await signInWithMicrosoft(role);
setStateAndStorage(setUser, 'user', user || null);
setStateAndStorage(setUserRole, 'userRole', role);
setStateAndStorage(setToken, 'firebaseIdToken', firebaseIdToken || null);
setStateAndStorage(setMsToken, 'msAccessToken', msAccessToken || null);
setStateAndStorage(setNeo4jDbName, 'neo4jDbName', neo4jDbName || null);
setStateAndStorage(setUserNode, 'userNode', userNode || null);
setStateAndStorage(setTldrawUserFilePath, 'tldrawUserFilePath', userNode?.path || null);
setStateAndStorage(setOneNoteNotebook, 'oneNoteNotebook', oneNoteNotebook || null);
console.log(`Signed in with Microsoft as ${role}`);
console.log("OneNote Notebook:", oneNoteNotebook);
setIsLoading(false);
return { user, userRole: role, firebaseIdToken, msAccessToken, userNode, oneNoteNotebook, message: isNewUser ? 'User registered successfully with Microsoft' : 'User logged in successfully with Microsoft' };
} catch (error) {
setError(error instanceof Error ? error.message : 'An unknown error occurred');
setIsLoading(false);
return { user: null, userRole: null, firebaseIdToken: null, msAccessToken: null, userNode: null, oneNoteNotebook: null, message: 'Failed to sign in with Microsoft' };
}
};
const getIdToken = async () => {
if (user) {
return await user.getIdToken(true);
}
throw new Error('User not authenticated');
};
return (
<AuthContext.Provider
value={{
user,
userRole,
firebaseIdToken,
msAccessToken,
userNode,
tldrawUserFilePath,
oneNoteNotebook,
login,
logout,
registerUser,
registerOrLoginWithMicrosoft,
error,
isLoading,
neo4jDbName,
clearError: () => setError(null),
getIdToken,
}}
>
{children}
</AuthContext.Provider>
);
};
export const useAuth = () => useContext(AuthContext);

35
tsconfig.base.json Normal file
View File

@ -0,0 +1,35 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"display": "Default",
"compilerOptions": {
"composite": true,
"declaration": true,
"declarationMap": true,
"emitDeclarationOnly": true,
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"importHelpers": true,
"resolveJsonModule": true,
"incremental": true,
"jsx": "react-jsx",
"lib": ["dom", "DOM.Iterable", "esnext"],
"experimentalDecorators": true,
"module": "esnext",
"target": "esnext",
"moduleResolution": "node",
"noFallthroughCasesInSwitch": true,
"noImplicitAny": true,
"noImplicitReturns": true,
"noUnusedLocals": false,
"noUnusedParameters": false,
"skipLibCheck": true,
"strict": true,
"strictFunctionTypes": true,
"strictNullChecks": true,
"useDefineForClassFields": true,
"noImplicitOverride": true,
"types": ["node", "@types/jest"]
},
"exclude": ["node_modules"]
}

16
tsconfig.json Normal file
View File

@ -0,0 +1,16 @@
{
"extends": "./tsconfig.base.json",
"include": ["src", "scripts", "vite.config.mts"],
"exclude": ["node_modules", "dist", ".tsbuild*"],
"compilerOptions": {
"target": "ESNext",
"module": "ESNext",
"moduleResolution": "node",
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"jsx": "react-jsx",
"types": ["bun-types", "node"],
"strict": true,
"skipLibCheck": true
}
}

16
vite.config.mts Normal file
View File

@ -0,0 +1,16 @@
import react from '@vitejs/plugin-react-swc'
import path from 'path'
import { defineConfig } from 'vite'
export default defineConfig(() => ({
plugins: [react({ tsDecorators: true })],
root: path.join(__dirname, 'src/client'),
publicDir: path.join(__dirname, 'public'),
server: {
port: 5000,
host: '0.0.0.0', // Expose the server to the network
},
optimizeDeps: {
exclude: ['assets'],
},
}))

3321
yarn.lock Normal file

File diff suppressed because it is too large Load Diff