This commit is contained in:
2025-11-11 14:19:03 -07:00
parent 7fec0424d7
commit 890e08d1b2
6 changed files with 46 additions and 16 deletions

View File

@@ -9,23 +9,15 @@ courses:
name: UX name: UX
- path: ./1425/2025-fall-alex/modules/ - path: ./1425/2025-fall-alex/modules/
name: "1425" name: "1425"
- path: ./3840_Telemetry/2025_spring_alex/modules/
name: Telem and Ops
- path: ./4850_AdvancedFE/2026-spring-alex/modules - path: ./4850_AdvancedFE/2026-spring-alex/modules
name: Adv Frontend Spring name: Adv Frontend Spring
- path: ./1400/2025_spring_alex/modules/
name: 1400-old
- path: ./1400/2026_spring_alex/modules - path: ./1400/2026_spring_alex/modules
name: "1400" name: "1400"
- path: ./1405/2025_spring_alex/
name: 1405 old
- path: ./1405/2026_spring_alex - path: ./1405/2026_spring_alex
name: "1405" name: "1405"
- path: ./1810/2026-spring-alex/modules - path: ./1810/2026-spring-alex/modules
name: Web Intro Spring name: Web Intro Spring
- path: ./3840_Telemetry/2026_spring_alex - path: ./3840_Telemetry/2026_spring_alex
name: Telem and Ops New name: Telem and Ops New
- path: ./4620_Distributed/2025Spring/modules/
name: distributed-old
- path: ./4620_Distributed/2026-spring-alex/modules - path: ./4620_Distributed/2026-spring-alex/modules
name: Distributed name: Distributed

View File

@@ -22,6 +22,7 @@ import {
} from "@/features/local/course/localCourseSettings"; } from "@/features/local/course/localCourseSettings";
import { useCourseListInTermQuery } from "@/features/canvas/hooks/canvasCourseHooks"; import { useCourseListInTermQuery } from "@/features/canvas/hooks/canvasCourseHooks";
import { useCanvasTermsQuery } from "@/features/canvas/hooks/canvasHooks"; import { useCanvasTermsQuery } from "@/features/canvas/hooks/canvasHooks";
import { useDirectoryExistsQuery } from "@/features/local/utils/storageDirectoryHooks";
const sampleCompose = `services: const sampleCompose = `services:
canvas_manager: canvas_manager:
@@ -62,7 +63,6 @@ export default function AddNewCourseToGlobalSettingsForm() {
const formIsComplete = const formIsComplete =
selectedTerm && selectedCanvasCourse && selectedDirectory; selectedTerm && selectedCanvasCourse && selectedDirectory;
return ( return (
<div> <div>
<ButtonSelect <ButtonSelect
@@ -95,7 +95,12 @@ export default function AddNewCourseToGlobalSettingsForm() {
disabled={!formIsComplete || createCourse.isPending} disabled={!formIsComplete || createCourse.isPending}
onClick={async () => { onClick={async () => {
if (formIsComplete) { if (formIsComplete) {
console.log("Creating course with settings:", selectedDirectory, "old course", courseToImport); console.log(
"Creating course with settings:",
selectedDirectory,
"old course",
courseToImport
);
const newSettings: LocalCourseSettings = courseToImport const newSettings: LocalCourseSettings = courseToImport
? { ? {
...courseToImport, ...courseToImport,
@@ -145,11 +150,6 @@ export default function AddNewCourseToGlobalSettingsForm() {
</button> </button>
</div> </div>
{createCourse.isPending && <Spinner />} {createCourse.isPending && <Spinner />}
{/* <pre>
<div>Example docker compose</div>
<code className="language-yml">{sampleCompose}</code>
</pre> */}
</div> </div>
); );
} }
@@ -185,6 +185,8 @@ function OtherSettings({
useCourseListInTermQuery(selectedTerm.id); useCourseListInTermQuery(selectedTerm.id);
const { data: allSettings } = useLocalCoursesSettingsQuery(); const { data: allSettings } = useLocalCoursesSettingsQuery();
const [directory, setDirectory] = useState("./"); const [directory, setDirectory] = useState("./");
const { data: directoryExists, isLoading: directoryExistsLoading } =
useDirectoryExistsQuery(directory);
const populatedCanvasCourseIds = allSettings?.map((s) => s.canvasId) ?? []; const populatedCanvasCourseIds = allSettings?.map((s) => s.canvasId) ?? [];
const availableCourses = const availableCourses =
@@ -224,6 +226,15 @@ function OtherSettings({
setLastTypedValue={setSelectedDirectory} setLastTypedValue={setSelectedDirectory}
label={"Storage Folder"} label={"Storage Folder"}
/> />
<div className="text-center mt-2 min-h-6">
{directoryExistsLoading && <Spinner />}
{!directoryExistsLoading && directoryExists && (
<div className="text-red-300">Directory must be a new folder</div>
)}
{!directoryExistsLoading && directoryExists === false && (
<div className="text-green-300"> New folder</div>
)}
</div>
<br /> <br />
<div className="flex justify-center"> <div className="flex justify-center">
<DayOfWeekInput <DayOfWeekInput

View File

@@ -47,7 +47,7 @@ export const CalendarMonth = ({ month }: { month: CalendarMonthModel }) => {
className={ className={
"text-2xl transition-all duration-500 " + "text-2xl transition-all duration-500 " +
"hover:text-slate-50 underline hover:scale-105 " + "hover:text-slate-50 underline hover:scale-105 " +
"flex " "flex cursor-pointer"
} }
onClick={() => setIsExpanded((e) => !e)} onClick={() => setIsExpanded((e) => !e)}
role="button" role="button"

View File

@@ -25,4 +25,13 @@ export const directoriesRouter = router({
.query(async ({ input: { folderPath } }) => { .query(async ({ input: { folderPath } }) => {
return await fileStorageService.settings.folderIsCourse(folderPath); return await fileStorageService.settings.folderIsCourse(folderPath);
}), }),
directoryExists: publicProcedure
.input(
z.object({
relativePath: z.string(),
})
)
.query(async ({ input: { relativePath } }) => {
return await fileStorageService.directoryExists(relativePath);
}),
}); });

View File

@@ -72,4 +72,15 @@ export const fileStorageService = {
} }
return { files, folders }; return { files, folders };
}, },
async directoryExists(relativePath: string): Promise<boolean> {
const fullPath = path.join(basePath, relativePath);
// Security: ensure fullPath is inside basePath
const resolvedBase = path.resolve(basePath);
const resolvedFull = path.resolve(fullPath);
if (!resolvedFull.startsWith(resolvedBase)) {
return false;
}
return await directoryOrFileExists(fullPath);
},
}; };

View File

@@ -23,3 +23,10 @@ export const useDirectoryIsCourseQuery = (folderPath: string) => {
trpc.directories.directoryIsCourse.queryOptions({ folderPath }) trpc.directories.directoryIsCourse.queryOptions({ folderPath })
); );
}; };
export const useDirectoryExistsQuery = (relativePath: string) => {
const trpc = useTRPC();
return useQuery(
trpc.directories.directoryExists.queryOptions({ relativePath })
);
};