mirror of
https://github.com/alexmickelson/canvasManagement.git
synced 2026-03-25 23:28:33 -06:00
pointer
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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);
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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 })
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user