first commit

This commit is contained in:
2025-04-24 13:11:28 +08:00
commit ff9c54d5e4
5960 changed files with 834111 additions and 0 deletions

View File

@@ -0,0 +1,155 @@
import { useCallback, useEffect } from 'react'
import { useUserContext } from '../../../shared/context/user-context'
import { useFileTreeData } from '../../../shared/context/file-tree-data-context'
import { useFileTreeSelectable } from '../contexts/file-tree-selectable'
import { findInTree, findInTreeOrThrow } from '../util/find-in-tree'
import { useIdeContext } from '@/shared/context/ide-context'
import { useSnapshotContext } from '@/features/ide-react/context/snapshot-context'
export function useFileTreeSocketListener(onDelete: (entity: any) => void) {
const user = useUserContext()
const {
dispatchRename,
dispatchDelete,
dispatchMove,
dispatchCreateFolder,
dispatchCreateDoc,
dispatchCreateFile,
fileTreeData,
} = useFileTreeData()
const { selectedEntityIds, selectedEntityParentIds, select, unselect } =
useFileTreeSelectable()
const { socket } = useIdeContext()
const { fileTreeFromHistory } = useSnapshotContext()
const selectEntityIfCreatedByUser = useCallback(
// hack to automatically re-open refreshed linked files
(entityId, entityName, userId) => {
// If the created entity's user exists and is the current user
if (userId && user?.id === userId) {
// And we're expecting a refreshed socket for this entity
if (window.expectingLinkedFileRefreshedSocketFor === entityName) {
// Then select it
select(entityId)
window.expectingLinkedFileRefreshedSocketFor = null
}
}
},
[user, select]
)
useEffect(() => {
if (fileTreeFromHistory) return
function handleDispatchRename(entityId: string, name: string) {
dispatchRename(entityId, name)
}
if (socket) socket.on('reciveEntityRename', handleDispatchRename)
return () => {
if (socket)
socket.removeListener('reciveEntityRename', handleDispatchRename)
}
}, [socket, dispatchRename, fileTreeFromHistory])
useEffect(() => {
if (fileTreeFromHistory) return
function handleDispatchDelete(entityId: string) {
const entity = findInTree(fileTreeData, entityId)
unselect(entityId)
if (selectedEntityParentIds.has(entityId)) {
// we're deleting a folder with a selected children so we need to
// unselect its selected children first
for (const selectedEntityId of selectedEntityIds) {
if (
findInTreeOrThrow(fileTreeData, selectedEntityId).path.includes(
entityId
)
) {
unselect(selectedEntityId)
}
}
}
dispatchDelete(entityId)
if (onDelete) {
onDelete(entity)
}
}
if (socket) socket.on('removeEntity', handleDispatchDelete)
return () => {
if (socket) socket.removeListener('removeEntity', handleDispatchDelete)
}
}, [
socket,
unselect,
dispatchDelete,
fileTreeData,
selectedEntityIds,
selectedEntityParentIds,
onDelete,
fileTreeFromHistory,
])
useEffect(() => {
if (fileTreeFromHistory) return
function handleDispatchMove(entityId: string, toFolderId: string) {
dispatchMove(entityId, toFolderId)
}
if (socket) socket.on('reciveEntityMove', handleDispatchMove)
return () => {
if (socket) socket.removeListener('reciveEntityMove', handleDispatchMove)
}
}, [socket, dispatchMove, fileTreeFromHistory])
useEffect(() => {
if (fileTreeFromHistory) return
function handleDispatchCreateFolder(parentFolderId: string, folder: any) {
dispatchCreateFolder(parentFolderId, folder)
}
if (socket) socket.on('reciveNewFolder', handleDispatchCreateFolder)
return () => {
if (socket)
socket.removeListener('reciveNewFolder', handleDispatchCreateFolder)
}
}, [socket, dispatchCreateFolder, fileTreeFromHistory])
useEffect(() => {
if (fileTreeFromHistory) return
function handleDispatchCreateDoc(
parentFolderId: string,
doc: any,
_source: unknown
) {
dispatchCreateDoc(parentFolderId, doc)
}
if (socket) socket.on('reciveNewDoc', handleDispatchCreateDoc)
return () => {
if (socket) socket.removeListener('reciveNewDoc', handleDispatchCreateDoc)
}
}, [socket, dispatchCreateDoc, fileTreeFromHistory])
useEffect(() => {
if (fileTreeFromHistory) return
function handleDispatchCreateFile(
parentFolderId: string,
file: any,
_source: unknown,
linkedFileData: any,
userId: string
) {
dispatchCreateFile(parentFolderId, file)
if (linkedFileData) {
selectEntityIfCreatedByUser(file._id, file.name, userId)
}
}
if (socket) socket.on('reciveNewFile', handleDispatchCreateFile)
return () => {
if (socket)
socket.removeListener('reciveNewFile', handleDispatchCreateFile)
}
}, [
socket,
dispatchCreateFile,
selectEntityIfCreatedByUser,
fileTreeFromHistory,
])
}

View File

@@ -0,0 +1,36 @@
import { useEffect, useState } from 'react'
import { getJSON } from '../../../infrastructure/fetch-json'
import { fileCollator } from '../util/file-collator'
import useAbortController from '../../../shared/hooks/use-abort-controller'
export type Entity = {
path: string
}
const alphabetical = (a: Entity, b: Entity) =>
fileCollator.compare(a.path, b.path)
export function useProjectEntities(projectId?: string) {
const [loading, setLoading] = useState(false)
const [data, setData] = useState<Entity[] | null>(null)
const [error, setError] = useState<any>(false)
const { signal } = useAbortController()
useEffect(() => {
if (projectId) {
setLoading(true)
setError(false)
setData(null)
getJSON(`/project/${projectId}/entities`, { signal })
.then(data => {
setData(data.entities.sort(alphabetical))
})
.catch(error => setError(error))
.finally(() => setLoading(false))
}
}, [projectId, signal])
return { loading, data, error }
}

View File

@@ -0,0 +1,58 @@
import { useEffect, useState } from 'react'
import { postJSON } from '../../../infrastructure/fetch-json'
import { fileCollator } from '../util/file-collator'
import useAbortController from '../../../shared/hooks/use-abort-controller'
export type OutputEntity = {
path: string
clsiServerId: string
compileGroup: string
build: string
}
const alphabetical = (a: OutputEntity, b: OutputEntity) =>
fileCollator.compare(a.path, b.path)
export function useProjectOutputFiles(projectId?: string) {
const [loading, setLoading] = useState<boolean>(false)
const [data, setData] = useState<OutputEntity[] | null>(null)
const [error, setError] = useState<any>(false)
const { signal } = useAbortController()
useEffect(() => {
if (projectId) {
setLoading(true)
setError(false)
setData(null)
postJSON(`/project/${projectId}/compile`, {
body: {
check: 'silent',
draft: false,
incrementalCompilesEnabled: false,
},
signal,
})
.then(data => {
if (data.status === 'success') {
const filteredFiles = data.outputFiles.filter(
(file: OutputEntity) =>
file.path.match(/.*\.(pdf|png|jpeg|jpg|gif)/)
)
data.outputFiles.forEach((file: OutputEntity) => {
file.clsiServerId = data.clsiServerId
file.compileGroup = data.compileGroup
})
setData(filteredFiles.sort(alphabetical))
} else {
setError('linked-project-compile-error')
}
})
.catch(error => setError(error))
.finally(() => setLoading(false))
}
}, [projectId, signal])
return { loading, data, error }
}

View File

@@ -0,0 +1,32 @@
import { useEffect, useState } from 'react'
import { getJSON } from '../../../infrastructure/fetch-json'
import { fileCollator } from '../util/file-collator'
import useAbortController from '../../../shared/hooks/use-abort-controller'
export type Project = {
_id: string
name: string
accessLevel: string
}
const alphabetical = (a: Project, b: Project) =>
fileCollator.compare(a.name, b.name)
export function useUserProjects() {
const [loading, setLoading] = useState(true)
const [data, setData] = useState<Project[] | null>(null)
const [error, setError] = useState<any>(false)
const { signal } = useAbortController()
useEffect(() => {
getJSON('/user/projects', { signal })
.then(data => {
setData(data.projects.sort(alphabetical))
})
.catch(error => setError(error))
.finally(() => setLoading(false))
}, [signal])
return { loading, data, error }
}