feat: migrate dashboard to svelte 5 (#577)
* chore: migration to SvelteKit 2 * stage 1: migrate to svelte 5 and fix rerendering issues * stage 2: fix more $: reactivity across dashboard * fix merge conflicts * chore: migrate code in main to svelte 5 * chore: update preventDefault * chore: simplify tsconfig and remove unused mobileMenuRef in MobileMenu.svelte * chore: enforce .ts across dashboard * rename from classroomio-com to website * stage-3: Replace carbon icon svelte with lucide icon * update lock file * chore: finish icon replacement * fix: settings and setup page multiple rerender * stage 3: More icon and rerendering fixes * feat: checkpoint * feat: update editor * [not working] connect everything with ui build * storybook + ui + dashboard all working independently * add bg-black to +page.svelte * feat: add editor to storybook * chore: animate lucid icons (#590) * v1 animations * feat: change some course navigation icons & animate org navigation icons * chore: change settings icon * chore: use css instead of js for hover * rename animations --------- Co-authored-by: tunny17 <tvnji01@gmail.com> * chore: update path to node_modules types in api tsconfig * fix: update import path for DropdownMenu component * fix: add rollupOptions to build configuration in vite.config.ts * fix: remove rollupOptions from build configuration and add @floating-ui/dom dependency * install --------- Co-authored-by: tunny17 <tvnji01@gmail.com>
This commit is contained in:
@@ -58,7 +58,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/katex": "^0.16.7",
|
||||
"@types/node": "^20.11.19",
|
||||
"@types/node": "^20.19.0",
|
||||
"@types/nodemailer": "^6.4.17",
|
||||
"@typescript-eslint/eslint-plugin": "7.0.1",
|
||||
"@typescript-eslint/parser": "7.0.1",
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
"skipLibCheck": true,
|
||||
"sourceMap": true,
|
||||
"target": "ESNext",
|
||||
"typeRoots": ["./node_modules/@types", "./src/types"],
|
||||
"typeRoots": ["../../node_modules/@types", "./node_modules/@types", "./src/types"],
|
||||
"types": ["node"]
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
module.exports = {
|
||||
presets: [['@babel/preset-env', { targets: { node: 'current' } }]]
|
||||
};
|
||||
@@ -21,6 +21,7 @@
|
||||
"dependencies": {
|
||||
"@carbon/charts-svelte": "^1.13.6",
|
||||
"@cio/api": "workspace:*",
|
||||
"@cio/ui": "workspace:*",
|
||||
"@lemonsqueezy/lemonsqueezy.js": "^2.2.0",
|
||||
"@lucide/svelte": "^0.545.0",
|
||||
"@polar-sh/sveltekit": "^0.2.1",
|
||||
@@ -34,6 +35,7 @@
|
||||
"@types/pluralize": "0.0.30",
|
||||
"ai": "^2.1.31",
|
||||
"axios": "^1.8.4",
|
||||
"bits-ui": "^2.14.1",
|
||||
"body-parser": "^1.20.2",
|
||||
"bufferutil": "^4.0.9",
|
||||
"canvas-confetti": "^1.6.0",
|
||||
@@ -78,8 +80,10 @@
|
||||
"@babel/runtime": "^7.22.6",
|
||||
"@eslint/compat": "^1.4.0",
|
||||
"@eslint/js": "^9.36.0",
|
||||
"@rollup/plugin-replace": "^6.0.2",
|
||||
"@sveltejs/kit": "^2.5.27",
|
||||
"@sveltejs/vite-plugin-svelte": "^4.0.0",
|
||||
"@tailwindcss/postcss": "^4.1.14",
|
||||
"@tailwindcss/typography": "^0.5.9",
|
||||
"@testing-library/jest-dom": "^6.1.5",
|
||||
"@testing-library/svelte": "^4.0.5",
|
||||
@@ -87,7 +91,6 @@
|
||||
"@types/lodash": "^4.14.196",
|
||||
"@types/node": "^20",
|
||||
"all-object-keys": "^2.2.0",
|
||||
"autoprefixer": "^10.4.14",
|
||||
"babel-jest": "^29.7.0",
|
||||
"carbon-components-svelte": "^0.79.0",
|
||||
"diff": "^5.2.0",
|
||||
@@ -107,7 +110,7 @@
|
||||
"svelte-jester": "^3.0.0",
|
||||
"svelte-loading-spinners": "^0.3.4",
|
||||
"svelte-preprocess": "^6.0.0",
|
||||
"tailwindcss": "^3.3.2",
|
||||
"tailwindcss": "^4.1.14",
|
||||
"ts-jest": "^29.1.1",
|
||||
"ts-node": "^10.9.2",
|
||||
"@cio/tsconfig": "workspace:*",
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
export default {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {}
|
||||
'@tailwindcss/postcss': {},
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
/*Write your global styles here, in PostCSS syntax */
|
||||
@tailwind base;
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
@import 'tailwindcss';
|
||||
|
||||
@import '@cio/ui/output.css';
|
||||
|
||||
@custom-variant dark (&:where(.dark, .dark *));
|
||||
|
||||
/** Global scrollbar styles */
|
||||
html {
|
||||
@@ -84,7 +85,7 @@ h6 {
|
||||
margin-bottom: 10px !important;
|
||||
}
|
||||
|
||||
.dark .prose h1,
|
||||
/* .dark .prose h1,
|
||||
.dark .prose h2,
|
||||
.dark .prose h3,
|
||||
.dark .prose h4,
|
||||
@@ -95,7 +96,7 @@ h6 {
|
||||
.dark .prose li::marker,
|
||||
.dark .prose p {
|
||||
color: #fff !important;
|
||||
}
|
||||
} */
|
||||
|
||||
iframe.iframe {
|
||||
width: 100%;
|
||||
@@ -192,10 +193,6 @@ svg.lucide-icon:not(.custom) {
|
||||
}
|
||||
}
|
||||
|
||||
@tailwind components;
|
||||
|
||||
@tailwind utilities;
|
||||
|
||||
/**
|
||||
Rose Theme
|
||||
*/
|
||||
@@ -710,3 +707,312 @@ svg.lucide-icon:not(.custom) {
|
||||
.theme-violet .border-primary-900 {
|
||||
border-color: #890684 !important;
|
||||
}
|
||||
|
||||
/* Tailwind v4 Custom Animations and Keyframes */
|
||||
@theme {
|
||||
--animate-shake-announcement: shakeAnnouncement 0.5s ease-in-out;
|
||||
--animate-flip-book: flipBook 0.6s ease-in-out;
|
||||
--animate-scroll-unroll: scrollUnroll 0.8s ease-out;
|
||||
--animate-check-items: attendanceCount 0.8s ease-in-out;
|
||||
--animate-task-complete: taskComplete 0.6s ease-in-out;
|
||||
--animate-grade-glow: gradeGlow 1s ease-in-out;
|
||||
--animate-people-wave: peopleWave 0.8s ease-in-out;
|
||||
--animate-chart-grow: dataScan 1s ease-in-out;
|
||||
--animate-screen-glow: screenGlow 1s ease-in-out;
|
||||
--animate-library-expand: libraryExpand 0.7s ease-out;
|
||||
--animate-template-morph: templateMorph 0.6s ease-in-out;
|
||||
--animate-ripple: communityRipple 0.9s ease-out;
|
||||
--animate-roll: quizRoll 0.8s ease-in-out;
|
||||
--animate-gather: audienceGather 0.7s ease-in-out;
|
||||
--animate-setup-configure: setupConfigure 1s ease-in-out;
|
||||
}
|
||||
|
||||
@keyframes shakeAnnouncement {
|
||||
0%,
|
||||
100% {
|
||||
transform: translateX(0);
|
||||
}
|
||||
10%,
|
||||
30%,
|
||||
50%,
|
||||
70%,
|
||||
90% {
|
||||
transform: translateX(-2px);
|
||||
}
|
||||
20%,
|
||||
40%,
|
||||
60%,
|
||||
80% {
|
||||
transform: translateX(2px);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes flipBook {
|
||||
0% {
|
||||
transform: rotateY(0deg);
|
||||
}
|
||||
50% {
|
||||
transform: rotateY(90deg);
|
||||
}
|
||||
100% {
|
||||
transform: rotateY(0deg);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes scrollUnroll {
|
||||
0% {
|
||||
transform: scaleY(0) translateY(-50%);
|
||||
opacity: 0.5;
|
||||
}
|
||||
50% {
|
||||
transform: scaleY(0.8) translateY(-25%);
|
||||
opacity: 0.8;
|
||||
}
|
||||
100% {
|
||||
transform: scaleY(1) translateY(0);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes attendanceCount {
|
||||
0% {
|
||||
transform: scale(1);
|
||||
opacity: 0.8;
|
||||
}
|
||||
20% {
|
||||
transform: scale(1.1);
|
||||
opacity: 1;
|
||||
}
|
||||
40% {
|
||||
transform: scale(1.05);
|
||||
opacity: 0.9;
|
||||
}
|
||||
60% {
|
||||
transform: scale(1.15);
|
||||
opacity: 1;
|
||||
}
|
||||
80% {
|
||||
transform: scale(1.02);
|
||||
opacity: 0.95;
|
||||
}
|
||||
100% {
|
||||
transform: scale(1);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes taskComplete {
|
||||
0% {
|
||||
transform: scale(1) rotate(0deg);
|
||||
}
|
||||
25% {
|
||||
transform: scale(1.1) rotate(-5deg);
|
||||
}
|
||||
50% {
|
||||
transform: scale(1.2) rotate(5deg);
|
||||
}
|
||||
75% {
|
||||
transform: scale(1.1) rotate(-2deg);
|
||||
}
|
||||
100% {
|
||||
transform: scale(1) rotate(0deg);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes gradeGlow {
|
||||
0%,
|
||||
100% {
|
||||
transform: scale(1);
|
||||
filter: brightness(1) drop-shadow(0 0 0 rgba(34, 197, 94, 0));
|
||||
}
|
||||
50% {
|
||||
transform: scale(1.15);
|
||||
filter: brightness(1.3) drop-shadow(0 0 8px rgba(34, 197, 94, 0.6));
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes peopleWave {
|
||||
0%,
|
||||
100% {
|
||||
transform: rotate(0deg) scale(1);
|
||||
}
|
||||
25% {
|
||||
transform: rotate(-5deg) scale(1.05);
|
||||
}
|
||||
50% {
|
||||
transform: rotate(5deg) scale(1.1);
|
||||
}
|
||||
75% {
|
||||
transform: rotate(-3deg) scale(1.05);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes dataScan {
|
||||
0% {
|
||||
transform: scale(1);
|
||||
filter: brightness(1) drop-shadow(0 0 0 rgba(99, 102, 241, 0));
|
||||
}
|
||||
25% {
|
||||
transform: scale(1.05);
|
||||
filter: brightness(1.1) drop-shadow(0 0 4px rgba(99, 102, 241, 0.4));
|
||||
}
|
||||
50% {
|
||||
transform: scale(1.1);
|
||||
filter: brightness(1.2) drop-shadow(0 0 8px rgba(99, 102, 241, 0.6));
|
||||
}
|
||||
75% {
|
||||
transform: scale(1.15) rotate(2deg);
|
||||
filter: brightness(1.3) drop-shadow(0 0 12px rgba(99, 102, 241, 0.8));
|
||||
}
|
||||
100% {
|
||||
transform: scale(1);
|
||||
filter: brightness(1) drop-shadow(0 0 0 rgba(99, 102, 241, 0));
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes screenGlow {
|
||||
0%,
|
||||
100% {
|
||||
transform: scale(1);
|
||||
filter: brightness(1) drop-shadow(0 0 0 rgba(59, 130, 246, 0));
|
||||
}
|
||||
50% {
|
||||
transform: scale(1.1);
|
||||
filter: brightness(1.2) drop-shadow(0 0 6px rgba(59, 130, 246, 0.5));
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes libraryExpand {
|
||||
0% {
|
||||
transform: translateY(-100%) scale(0.8);
|
||||
opacity: 0;
|
||||
}
|
||||
30% {
|
||||
transform: translateY(-20%) scale(0.9);
|
||||
opacity: 0.7;
|
||||
}
|
||||
60% {
|
||||
transform: translateY(5%) scale(1.05);
|
||||
opacity: 1;
|
||||
}
|
||||
80% {
|
||||
transform: translateY(-2%) scale(1.02);
|
||||
opacity: 1;
|
||||
}
|
||||
100% {
|
||||
transform: translateY(0%) scale(1);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes templateMorph {
|
||||
0% {
|
||||
transform: scale(1) rotate(0deg);
|
||||
}
|
||||
25% {
|
||||
transform: scale(1.05) rotate(-2deg) skewX(2deg);
|
||||
}
|
||||
50% {
|
||||
transform: scale(1.1) rotate(2deg) skewX(-2deg);
|
||||
}
|
||||
75% {
|
||||
transform: scale(1.05) rotate(-1deg) skewX(1deg);
|
||||
}
|
||||
100% {
|
||||
transform: scale(1) rotate(0deg) skewX(0deg);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes communityRipple {
|
||||
0% {
|
||||
transform: translateX(-100%) scale(0.8);
|
||||
opacity: 0;
|
||||
}
|
||||
30% {
|
||||
transform: translateX(-20%) scale(0.9);
|
||||
opacity: 0.7;
|
||||
}
|
||||
60% {
|
||||
transform: translateX(5%) scale(1.05);
|
||||
opacity: 1;
|
||||
}
|
||||
80% {
|
||||
transform: translateX(-2%) scale(1.02);
|
||||
opacity: 1;
|
||||
}
|
||||
100% {
|
||||
transform: translateX(0%) scale(1);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes quizRoll {
|
||||
0% {
|
||||
transform: scale(1) rotate(0deg);
|
||||
}
|
||||
20% {
|
||||
transform: scale(1.05) rotate(72deg);
|
||||
}
|
||||
40% {
|
||||
transform: scale(1.1) rotate(144deg);
|
||||
}
|
||||
60% {
|
||||
transform: scale(1.15) rotate(216deg);
|
||||
}
|
||||
80% {
|
||||
transform: scale(1.1) rotate(288deg);
|
||||
}
|
||||
100% {
|
||||
transform: scale(1) rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes audienceGather {
|
||||
0% {
|
||||
transform: scale(1);
|
||||
filter: brightness(1) drop-shadow(0 0 0 rgba(147, 51, 234, 0));
|
||||
}
|
||||
25% {
|
||||
transform: scale(0.95) scaleX(1.1);
|
||||
filter: brightness(1.05) drop-shadow(0 0 2px rgba(147, 51, 234, 0.3));
|
||||
}
|
||||
50% {
|
||||
transform: scale(1.1) scaleX(0.9);
|
||||
filter: brightness(1.1) drop-shadow(0 0 4px rgba(147, 51, 234, 0.5));
|
||||
}
|
||||
75% {
|
||||
transform: scale(1.05) scaleX(1.05);
|
||||
filter: brightness(1.05) drop-shadow(0 0 2px rgba(147, 51, 234, 0.3));
|
||||
}
|
||||
100% {
|
||||
transform: scale(1) scaleX(1);
|
||||
filter: brightness(1) drop-shadow(0 0 0 rgba(147, 51, 234, 0));
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes setupConfigure {
|
||||
0% {
|
||||
transform: scale(1) rotate(0deg);
|
||||
}
|
||||
15% {
|
||||
transform: scale(1.02) rotate(15deg);
|
||||
}
|
||||
30% {
|
||||
transform: scale(1.04) rotate(-10deg);
|
||||
}
|
||||
45% {
|
||||
transform: scale(1.06) rotate(20deg);
|
||||
}
|
||||
60% {
|
||||
transform: scale(1.08) rotate(-15deg);
|
||||
}
|
||||
75% {
|
||||
transform: scale(1.06) rotate(10deg);
|
||||
}
|
||||
90% {
|
||||
transform: scale(1.02) rotate(-5deg);
|
||||
}
|
||||
100% {
|
||||
transform: scale(1) rotate(0deg);
|
||||
}
|
||||
}
|
||||
+23
-25
@@ -1,33 +1,31 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
|
||||
<meta name="theme-color" content="#333333" />
|
||||
|
||||
<link rel="manifest" href="/manifest.json" crossorigin="use-credentials" />
|
||||
<link rel="icon" type="image/png" href="/favicon.ico" />
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/logo-32.png" />
|
||||
<link rel="stylesheet" href="https://cdn.plyr.io/3.7.3/plyr.css" />
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
|
||||
<meta name="theme-color" content="#333333" />
|
||||
|
||||
<link rel="manifest" href="/manifest.json" crossorigin="use-credentials" />
|
||||
<link rel="icon" type="image/png" href="/favicon.ico" />
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/logo-32.png" />
|
||||
<link rel="stylesheet" href="https://cdn.plyr.io/3.7.3/plyr.css" />
|
||||
<link rel="stylesheet" href="https://assets.cdn.clsrio.com/carbon.css" />
|
||||
<link rel="manifest" href="/manifest.json" crossorigin="use-credentials" />
|
||||
<link rel="icon" type="image/png" href="/favicon.ico" />
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/logo-32.png" />
|
||||
<link rel="stylesheet" href="https://cdn.plyr.io/3.7.3/plyr.css" />
|
||||
|
||||
<script
|
||||
defer
|
||||
src="https://umami.hz.oncws.com/script.js"
|
||||
data-website-id="80a9544a-4dda-4c91-b62f-b6be7a8a3b5c"
|
||||
></script>
|
||||
<link rel="manifest" href="/manifest.json" crossorigin="use-credentials" />
|
||||
<link rel="icon" type="image/png" href="/favicon.ico" />
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/logo-32.png" />
|
||||
<link rel="stylesheet" href="https://cdn.plyr.io/3.7.3/plyr.css" />
|
||||
|
||||
<script src="https://cdn.plyr.io/3.7.3/plyr.js"></script>
|
||||
<script defer src="https://umami.hz.oncws.com/script.js"
|
||||
data-website-id="80a9544a-4dda-4c91-b62f-b6be7a8a3b5c"></script>
|
||||
|
||||
%sveltekit.head%
|
||||
</head>
|
||||
<script src="https://cdn.plyr.io/3.7.3/plyr.js"></script>
|
||||
|
||||
<body data-sveltekit-preload-data="hover" class="">
|
||||
<div style="display: contents">%sveltekit.body%</div>
|
||||
</body>
|
||||
</html>
|
||||
%sveltekit.head%
|
||||
</head>
|
||||
|
||||
<body data-sveltekit-preload-data="hover" class="">
|
||||
<div style="display: contents">%sveltekit.body%</div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -23,7 +23,7 @@
|
||||
import { snackbar } from '$lib/components/Snackbar/store';
|
||||
import TabContent from '$lib/components/TabContent/index.svelte';
|
||||
import Tabs from '$lib/components/Tabs/index.svelte';
|
||||
import TextEditor from '$lib/components/TextEditor/index.svelte';
|
||||
// import TextEditor from '$lib/components/TextEditor/index.svelte';
|
||||
import MODES from '$lib/utils/constants/mode';
|
||||
import { formatYoutubeVideo } from '$lib/utils/functions/formatYoutubeVideo';
|
||||
import { supabase } from '$lib/utils/functions/supabase';
|
||||
@@ -67,16 +67,27 @@
|
||||
toggleMode = () => {}
|
||||
}: Props = $props();
|
||||
|
||||
// let content = $state<EdraEditorProps['Content']>();
|
||||
// let editor = $state<EdraEditorProps['Editor']>();
|
||||
// function onUpdate() {
|
||||
// content = editor?.getHTML();
|
||||
// console.log('content updated:', content);
|
||||
// if (mode === MODES.view) return;
|
||||
|
||||
// $lessonByTranslation[lessonId][$lesson.locale] = content;
|
||||
|
||||
// try {
|
||||
// localStorage.setItem(`lesson-${lessonId}-${$lesson.locale}`, content);
|
||||
// } catch (error) {}
|
||||
// $isLessonDirty = true;
|
||||
// }
|
||||
|
||||
let localeExists: Record<string, boolean> = {};
|
||||
// let prevContent = '';
|
||||
let timeoutId: NodeJS.Timeout;
|
||||
let errors: Record<string, string> = {};
|
||||
let editorWindowRef: Window | undefined = $state();
|
||||
let aiButtonRef: HTMLDivElement | undefined = $state();
|
||||
let openPopover = $state(false);
|
||||
let player: HTMLVideoElement | undefined = $state();
|
||||
let aiButtonClass =
|
||||
'flex items-center gap-2 px-5 py-2 border border-gray-300 hover:bg-gray-200 dark:hover:bg-gray-700 rounded-md w-full mb-2';
|
||||
// let editorWindowRef: Window | undefined = $state();
|
||||
('flex items-center gap-2 px-5 py-2 border border-gray-300 hover:bg-gray-200 dark:hover:bg-gray-700 rounded-md w-full mb-2');
|
||||
|
||||
const lessonTitle = $derived($lesson.title);
|
||||
|
||||
@@ -324,7 +335,7 @@
|
||||
initPlyr(player, $lesson.materials.videos);
|
||||
});
|
||||
|
||||
let editorValue = $derived(
|
||||
let _editorValue = $derived(
|
||||
lessonFallbackNote($lesson.materials.note, $lessonByTranslation[lessonId], $lesson.locale)
|
||||
);
|
||||
</script>
|
||||
@@ -403,7 +414,26 @@
|
||||
</div>
|
||||
|
||||
<div class="mt-5 h-[60vh]">
|
||||
<TextEditor
|
||||
<!-- <div class="bg-background z-50 mt-12 size-full max-w-5xl rounded-md border border-dashed">
|
||||
{#if editor && !editor.isDestroyed}
|
||||
<EdraToolBar
|
||||
class="bg-secondary/50 flex w-full items-center overflow-x-auto border-b border-dashed p-0.5"
|
||||
{editor}
|
||||
/>
|
||||
{:else}
|
||||
<div>
|
||||
Debug: Editor state - {editor ? 'exists' : 'undefined'}, destroyed: {editor?.isDestroyed || 'N/A'}
|
||||
</div>
|
||||
{/if}
|
||||
<EdraEditor
|
||||
bind:editor
|
||||
content={editorValue}
|
||||
autofocus={true}
|
||||
class="h-[300px] outline-none"
|
||||
{onUpdate}
|
||||
/>
|
||||
</div> -->
|
||||
<!-- <TextEditor
|
||||
id={lessonId}
|
||||
bind:editorWindowRef
|
||||
value={editorValue}
|
||||
@@ -419,7 +449,7 @@
|
||||
$isLessonDirty = true;
|
||||
}}
|
||||
placeholder={$t('course.navItem.lessons.materials.tabs.note.placeholder')}
|
||||
/>
|
||||
/> -->
|
||||
</div>
|
||||
</TabContent>
|
||||
|
||||
|
||||
@@ -79,7 +79,7 @@
|
||||
|
||||
<div class={className}>
|
||||
<button
|
||||
class="item relative flex items-center {btnPadding} mb-1 ml-2 {NavClasses.item} {isGroupActive &&
|
||||
class="item group relative flex items-center {btnPadding} mb-1 ml-2 {NavClasses.item} {isGroupActive &&
|
||||
!isLoading &&
|
||||
NavClasses.active} w-[95%]"
|
||||
tabindex="0"
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
<script lang="ts">
|
||||
import TableOfContentsIcon from '@lucide/svelte/icons/table-of-contents'; // overview
|
||||
import TableOfContentsIcon from '@lucide/svelte/icons/table-of-contents';
|
||||
import BookCheckIcon from '@lucide/svelte/icons/book-check';
|
||||
import ListTodoIcon from '@lucide/svelte/icons/list-todo';
|
||||
import UsersIcon from '@lucide/svelte/icons/users';
|
||||
import ScreenShareIcon from '@lucide/svelte/icons/screen-share';
|
||||
import AwardIcon from '@lucide/svelte/icons/award';
|
||||
import ScrollTextIcon from '@lucide/svelte/icons/scroll-text';
|
||||
import SettingsIcon from '@lucide/svelte/icons/settings';
|
||||
import MegaphoneIcon from '@lucide/svelte/icons/megaphone';
|
||||
import ChartLineIcon from '@lucide/svelte/icons/chart-line';
|
||||
import ListIcon from '@lucide/svelte/icons/list';
|
||||
import SquareLibrary from '@lucide/svelte/icons/square-library';
|
||||
import BookOpenCheck from '@lucide/svelte/icons/book-open-check';
|
||||
import PanelTop from '@lucide/svelte/icons/panel-top';
|
||||
import { NAV_IDS } from './constants';
|
||||
|
||||
interface Props {
|
||||
@@ -20,25 +20,25 @@
|
||||
</script>
|
||||
|
||||
{#if name === NAV_IDS.SECTION}
|
||||
<TableOfContentsIcon size={16} />
|
||||
<TableOfContentsIcon size={16} class="group-hover:animate-flip-book" />
|
||||
{:else if name === NAV_IDS.NEWS_FEED}
|
||||
<MegaphoneIcon size={16} />
|
||||
<MegaphoneIcon size={16} class="group-hover:animate-shake-announcement" />
|
||||
{:else if name === NAV_IDS.LESSONS}
|
||||
<ScrollTextIcon size={16} />
|
||||
<ScrollTextIcon size={16} class="group-hover:animate-scroll-unroll" />
|
||||
{:else if name === NAV_IDS.ATTENDANCE}
|
||||
<ListIcon size={16} />
|
||||
<BookOpenCheck size={16} class="group-hover:animate-check-items" />
|
||||
{:else if name === NAV_IDS.SUBMISSIONS}
|
||||
<ListTodoIcon size={16} />
|
||||
<SquareLibrary size={16} class="group-hover:animate-task-complete" />
|
||||
{:else if name === NAV_IDS.MARKS}
|
||||
<BookCheckIcon size={16} />
|
||||
<BookCheckIcon size={16} class="group-hover:animate-grade-glow" />
|
||||
{:else if name === NAV_IDS.PEOPLE}
|
||||
<UsersIcon size={16} />
|
||||
<UsersIcon size={16} class="group-hover:animate-people-wave" />
|
||||
{:else if name === NAV_IDS.ANALYTICS}
|
||||
<ChartLineIcon size={16} />
|
||||
<ChartLineIcon size={16} class="group-hover:animate-chart-grow" />
|
||||
{:else if name === NAV_IDS.LANDING_PAGE}
|
||||
<ScreenShareIcon size={16} />
|
||||
<PanelTop size={16} class="group-hover:animate-screen-glow" />
|
||||
{:else if name === NAV_IDS.CERTIFICATES}
|
||||
<AwardIcon size={16} />
|
||||
<AwardIcon size={16} class="group-hover:animate-bounce" />
|
||||
{:else if name === NAV_IDS.SETTINGS}
|
||||
<SettingsIcon size={16} />
|
||||
<SettingsIcon size={16} class="group-hover:animate-spin" />
|
||||
{/if}
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
}
|
||||
});
|
||||
|
||||
const result = (await response.json());
|
||||
const result = await response.json();
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(result.error || $t('courses.copy_course.error.failed_to_clone'));
|
||||
|
||||
@@ -34,11 +34,6 @@
|
||||
emptyDescription = $t('courses.course_card.empty_description'),
|
||||
isExplore = false
|
||||
}: Props = $props();
|
||||
|
||||
export let courses: Course[] = [];
|
||||
export let emptyTitle = $t('courses.course_card.empty_title');
|
||||
export let emptyDescription = $t('courses.course_card.empty_description');
|
||||
export let isExplore = false;
|
||||
|
||||
function calcProgressRate(progressRate?: number, totalLessons?: number): number {
|
||||
if (!progressRate || !totalLessons) {
|
||||
@@ -55,19 +50,19 @@
|
||||
|
||||
try {
|
||||
await deleteCourse($deleteCourseModal.id);
|
||||
|
||||
|
||||
// Remove the course from the courses store
|
||||
$coursesStore = $coursesStore.filter(course => course.id !== $deleteCourseModal.id);
|
||||
|
||||
$coursesStore = $coursesStore.filter((course) => course.id !== $deleteCourseModal.id);
|
||||
|
||||
// Show success message
|
||||
snackbar.success('snackbar.course_deleted');
|
||||
|
||||
|
||||
// Close modal and reset state
|
||||
deleteCourseModal.set(deleteCourseModalInitialState);
|
||||
} catch (error) {
|
||||
console.error('Error deleting course:', error);
|
||||
snackbar.error('snackbar.course_settings.error.went_wrong');
|
||||
|
||||
|
||||
// Stop deleting state on error
|
||||
$deleteCourseModal.isDeleting = false;
|
||||
}
|
||||
@@ -75,8 +70,8 @@
|
||||
</script>
|
||||
|
||||
<CopyCourseModal />
|
||||
<DeleteModal
|
||||
onDelete={handleDeleteCourse}
|
||||
<DeleteModal
|
||||
onDelete={handleDeleteCourse}
|
||||
bind:open={$deleteCourseModal.open}
|
||||
isLoading={$deleteCourseModal.isDeleting}
|
||||
/>
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { Editor } from '@cio/ui/custom/editor';
|
||||
import defaultContent from './default-content';
|
||||
</script>
|
||||
|
||||
<Editor content={defaultContent} />
|
||||
@@ -0,0 +1,825 @@
|
||||
export default {
|
||||
type: 'doc',
|
||||
content: [
|
||||
{
|
||||
type: 'heading',
|
||||
attrs: {
|
||||
textAlign: 'center',
|
||||
level: 1
|
||||
},
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: 'Introducing ClassroomIO'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
type: 'paragraph',
|
||||
attrs: {
|
||||
textAlign: 'justify',
|
||||
class: null
|
||||
},
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: 'The AI native LMS. We have '
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
marks: [
|
||||
{
|
||||
type: 'bold'
|
||||
}
|
||||
],
|
||||
text: 'bold'
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
text: ', '
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
marks: [
|
||||
{
|
||||
type: 'italic'
|
||||
}
|
||||
],
|
||||
text: 'italic'
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
text: ', '
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
marks: [
|
||||
{
|
||||
type: 'strike'
|
||||
}
|
||||
],
|
||||
text: 'strike'
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
text: ', '
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
marks: [
|
||||
{
|
||||
type: 'underline'
|
||||
}
|
||||
],
|
||||
text: 'underline'
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
text: ', '
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
marks: [
|
||||
{
|
||||
type: 'link',
|
||||
attrs: {
|
||||
href: 'https://github.com',
|
||||
target: '_blank',
|
||||
rel: 'noopener noreferrer',
|
||||
class: null
|
||||
}
|
||||
}
|
||||
],
|
||||
text: 'link'
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
text: ', '
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
marks: [
|
||||
{
|
||||
type: 'code'
|
||||
}
|
||||
],
|
||||
text: 'code'
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
text: ', Superscript A'
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
marks: [
|
||||
{
|
||||
type: 'superscript'
|
||||
}
|
||||
],
|
||||
text: 'b'
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
text: ', subscript A'
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
marks: [
|
||||
{
|
||||
type: 'subscript'
|
||||
}
|
||||
],
|
||||
text: 'b'
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
text: ', '
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
marks: [
|
||||
{
|
||||
type: 'highlight',
|
||||
attrs: {
|
||||
color: '#800080'
|
||||
}
|
||||
}
|
||||
],
|
||||
text: 'highlight1'
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
marks: [
|
||||
{
|
||||
type: 'highlight',
|
||||
attrs: {
|
||||
color: '#1a1600'
|
||||
}
|
||||
}
|
||||
],
|
||||
text: ','
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
text: ' '
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
marks: [
|
||||
{
|
||||
type: 'highlight',
|
||||
attrs: {
|
||||
color: '#0000FF'
|
||||
}
|
||||
}
|
||||
],
|
||||
text: 'highlight2'
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
text: ', '
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
marks: [
|
||||
{
|
||||
type: 'textStyle',
|
||||
attrs: {
|
||||
color: '#008000',
|
||||
fontSize: ''
|
||||
}
|
||||
}
|
||||
],
|
||||
text: 'textcolor1'
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
marks: [
|
||||
{
|
||||
type: 'textStyle',
|
||||
attrs: {
|
||||
color: 'rgb(78, 211, 21)',
|
||||
fontSize: ''
|
||||
}
|
||||
}
|
||||
],
|
||||
text: ', '
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
marks: [
|
||||
{
|
||||
type: 'textStyle',
|
||||
attrs: {
|
||||
color: '#FFA500',
|
||||
fontSize: ''
|
||||
}
|
||||
}
|
||||
],
|
||||
text: 'textcolor2'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
type: 'blockquote',
|
||||
content: [
|
||||
{
|
||||
type: 'paragraph',
|
||||
attrs: {
|
||||
textAlign: 'justify',
|
||||
class: null
|
||||
},
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: 'This is a block Quote'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
type: 'codeBlock',
|
||||
attrs: {
|
||||
language: 'javascript'
|
||||
},
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: 'console.log("Hello World")'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
type: 'horizontalRule'
|
||||
},
|
||||
{
|
||||
type: 'heading',
|
||||
attrs: {
|
||||
textAlign: 'left',
|
||||
level: 2
|
||||
},
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: 'Support For Media Placeholders and Medias'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
type: 'image-placeholder'
|
||||
},
|
||||
{
|
||||
type: 'image',
|
||||
attrs: {
|
||||
src: 'https://placehold.co/800x400/6A00F5/white',
|
||||
alt: null,
|
||||
title: 'A Sample Image',
|
||||
width: 658,
|
||||
height: null,
|
||||
align: 'center'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'video-placeholder'
|
||||
},
|
||||
{
|
||||
type: 'video',
|
||||
attrs: {
|
||||
src: 'https://videos.pexels.com/video-files/2491284/2491284-uhd_2732_1440_24fps.mp4',
|
||||
alt: null,
|
||||
title: 'A Sample Video',
|
||||
width: 673,
|
||||
height: null,
|
||||
align: 'center'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'audio-placeholder'
|
||||
},
|
||||
{
|
||||
type: 'audio',
|
||||
attrs: {
|
||||
src: 'https://cdn.pixabay.com/audio/2024/10/30/audio_42e6870f29.mp3',
|
||||
alt: null,
|
||||
title: 'A Sample Audio',
|
||||
width: 670,
|
||||
height: null,
|
||||
align: 'center'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'horizontalRule'
|
||||
},
|
||||
{
|
||||
type: 'heading',
|
||||
attrs: {
|
||||
textAlign: null,
|
||||
level: 2
|
||||
},
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: 'IFrames'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
type: 'iframe-placeholder'
|
||||
},
|
||||
{
|
||||
type: 'iframe',
|
||||
attrs: {
|
||||
src: 'https://www.google.com/maps/embed?pb=!1m18!1m12!1m3!1d3748.5645078258503!2d75.17451197505285!3d20.026784381383496!2m3!1f0!2f0!3f0!3m2!1i1024!2i768!4f13.1!3m3!1m2!1s0x3bdb93bd138ae4bd%3A0x574c6482cf0b89cf!2sEllora%20Caves!5e0!3m2!1sen!2sin!4v1742466514831!5m2!1sen!2sin',
|
||||
alt: null,
|
||||
title: 'Ellora Caves, Maharastra, India',
|
||||
width: 621,
|
||||
height: null,
|
||||
align: 'center'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'iframe',
|
||||
attrs: {
|
||||
src: 'https://www.youtube.com/embed/jfKfPfyJRdk?si=SYBE0PkI5v79-54W',
|
||||
alt: null,
|
||||
title: 'Lofi Hip Hop Youtube Streaming Video',
|
||||
width: 623,
|
||||
height: null,
|
||||
align: 'center'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'paragraph',
|
||||
attrs: {
|
||||
textAlign: null,
|
||||
class: null
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'horizontalRule'
|
||||
},
|
||||
{
|
||||
type: 'heading',
|
||||
attrs: {
|
||||
textAlign: null,
|
||||
level: 2
|
||||
},
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: 'Typography'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
type: 'paragraph',
|
||||
attrs: {
|
||||
textAlign: null,
|
||||
class: null
|
||||
},
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: 'Smilipicker, type '
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
marks: [
|
||||
{
|
||||
type: 'code'
|
||||
}
|
||||
],
|
||||
text: ':)'
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
text: ' and it becomes 🙂 or 😉. Typography support e.g. type '
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
marks: [
|
||||
{
|
||||
type: 'code'
|
||||
}
|
||||
],
|
||||
text: '!='
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
text: ' and it becomes ≠. Similarly (c) becomes ©, -> becomes → and many more like 1×2, ½. We also have color visualizer. #FFF, #000, #FF00FF can be visualized.'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
type: 'horizontalRule'
|
||||
},
|
||||
{
|
||||
type: 'heading',
|
||||
attrs: {
|
||||
textAlign: null,
|
||||
level: 2
|
||||
},
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: 'Tables'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
type: 'table',
|
||||
content: [
|
||||
{
|
||||
type: 'tableRow',
|
||||
content: [
|
||||
{
|
||||
type: 'tableCell',
|
||||
attrs: {
|
||||
colspan: 1,
|
||||
rowspan: 1,
|
||||
colwidth: [323],
|
||||
style: null
|
||||
},
|
||||
content: [
|
||||
{
|
||||
type: 'paragraph',
|
||||
attrs: {
|
||||
textAlign: null,
|
||||
class: null
|
||||
},
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: 'Hello World'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
type: 'tableCell',
|
||||
attrs: {
|
||||
colspan: 1,
|
||||
rowspan: 1,
|
||||
colwidth: [328],
|
||||
style: null
|
||||
},
|
||||
content: [
|
||||
{
|
||||
type: 'paragraph',
|
||||
attrs: {
|
||||
textAlign: null,
|
||||
class: null
|
||||
},
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: 'Hello World'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
type: 'tableCell',
|
||||
attrs: {
|
||||
colspan: 1,
|
||||
rowspan: 1,
|
||||
colwidth: [330],
|
||||
style: null
|
||||
},
|
||||
content: [
|
||||
{
|
||||
type: 'paragraph',
|
||||
attrs: {
|
||||
textAlign: null,
|
||||
class: null
|
||||
},
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: 'Hello World'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
type: 'tableRow',
|
||||
content: [
|
||||
{
|
||||
type: 'tableCell',
|
||||
attrs: {
|
||||
colspan: 1,
|
||||
rowspan: 1,
|
||||
colwidth: [323],
|
||||
style: null
|
||||
},
|
||||
content: [
|
||||
{
|
||||
type: 'paragraph',
|
||||
attrs: {
|
||||
textAlign: null,
|
||||
class: null
|
||||
},
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: 'Hello World'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
type: 'tableCell',
|
||||
attrs: {
|
||||
colspan: 1,
|
||||
rowspan: 1,
|
||||
colwidth: [328],
|
||||
style: null
|
||||
},
|
||||
content: [
|
||||
{
|
||||
type: 'paragraph',
|
||||
attrs: {
|
||||
textAlign: null,
|
||||
class: null
|
||||
},
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: 'Hello World'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
type: 'tableCell',
|
||||
attrs: {
|
||||
colspan: 1,
|
||||
rowspan: 1,
|
||||
colwidth: [330],
|
||||
style: null
|
||||
},
|
||||
content: [
|
||||
{
|
||||
type: 'paragraph',
|
||||
attrs: {
|
||||
textAlign: null,
|
||||
class: null
|
||||
},
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: 'Hello World'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
type: 'heading',
|
||||
attrs: {
|
||||
textAlign: null,
|
||||
level: 3
|
||||
},
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
marks: [
|
||||
{
|
||||
type: 'textStyle',
|
||||
attrs: {
|
||||
color: 'var(--tw-prose-headings)',
|
||||
fontSize: '1.5em'
|
||||
}
|
||||
}
|
||||
],
|
||||
text: 'Rending Math and '
|
||||
},
|
||||
{
|
||||
type: 'inlineMath',
|
||||
attrs: {
|
||||
latex: '\\LaTeX',
|
||||
evaluate: 'no',
|
||||
display: 'no'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
type: 'paragraph',
|
||||
attrs: {
|
||||
textAlign: null,
|
||||
class: null
|
||||
},
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: 'The editor supports the '
|
||||
},
|
||||
{
|
||||
type: 'inlineMath',
|
||||
attrs: {
|
||||
latex: '\\LaTeX',
|
||||
evaluate: 'no',
|
||||
display: 'no'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
text: ' rendering. e.g.'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
type: 'orderedList',
|
||||
attrs: {
|
||||
start: 1
|
||||
},
|
||||
content: [
|
||||
{
|
||||
type: 'listItem',
|
||||
content: [
|
||||
{
|
||||
type: 'paragraph',
|
||||
attrs: {
|
||||
textAlign: null,
|
||||
class: null
|
||||
},
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: 'Functions like, '
|
||||
},
|
||||
{
|
||||
type: 'inlineMath',
|
||||
attrs: {
|
||||
latex: 'sin²\\theta + cos²\\theta=1, a²+b²=c²',
|
||||
evaluate: 'no',
|
||||
display: 'no'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
type: 'listItem',
|
||||
content: [
|
||||
{
|
||||
type: 'paragraph',
|
||||
attrs: {
|
||||
textAlign: null,
|
||||
class: null
|
||||
},
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: 'Matrix like unit matrix'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
type: 'paragraph',
|
||||
attrs: {
|
||||
textAlign: null,
|
||||
class: null
|
||||
},
|
||||
content: [
|
||||
{
|
||||
type: 'inlineMath',
|
||||
attrs: {
|
||||
latex: '\\begin{pmatrix} 1 & 0 \\\\ 0 & 1 \\end{pmatrix}',
|
||||
evaluate: 'no',
|
||||
display: 'no'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
type: 'paragraph',
|
||||
attrs: {
|
||||
textAlign: null,
|
||||
class: null
|
||||
},
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: 'or more matrices like'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
type: 'paragraph',
|
||||
attrs: {
|
||||
textAlign: null,
|
||||
class: null
|
||||
},
|
||||
content: [
|
||||
{
|
||||
type: 'inlineMath',
|
||||
attrs: {
|
||||
latex:
|
||||
'\\begin{pmatrix} 1 & 0 & \\cdots & 0 \\\\ 0 & 1 & \\cdots & 0 \\\\ \\vdots & \\vdots & \\ddots & \\vdots \\\\ 0 & 0 & \\cdots & 1 \\end{pmatrix}',
|
||||
evaluate: 'no',
|
||||
display: 'no'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
type: 'listItem',
|
||||
content: [
|
||||
{
|
||||
type: 'paragraph',
|
||||
attrs: {
|
||||
textAlign: null,
|
||||
class: null
|
||||
},
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: 'Integrals and differential'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
type: 'paragraph',
|
||||
attrs: {
|
||||
textAlign: null,
|
||||
class: null
|
||||
},
|
||||
content: [
|
||||
{
|
||||
type: 'inlineMath',
|
||||
attrs: {
|
||||
latex:
|
||||
'\\int_{0}^{\\frac{\\pi}{2}} \\sin(x) \\,dx = \\left[-\\cos(x)\\right]_0^{\\frac{\\pi}{2}} = -\\cos\\left(\\frac{\\pi}{2}\\right) + \\cos(0)',
|
||||
evaluate: 'no',
|
||||
display: 'no'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
type: 'paragraph',
|
||||
attrs: {
|
||||
textAlign: null,
|
||||
class: null
|
||||
},
|
||||
content: [
|
||||
{
|
||||
type: 'inlineMath',
|
||||
attrs: {
|
||||
latex: '\\frac{\\mathrm{d}}{\\mathrm{d}x}(\\sin x) = \\cos x',
|
||||
evaluate: 'no',
|
||||
display: 'no'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
type: 'heading',
|
||||
attrs: {
|
||||
textAlign: null,
|
||||
level: 3
|
||||
},
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: 'Explore More…'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
type: 'paragraph',
|
||||
attrs: {
|
||||
textAlign: null,
|
||||
class: null
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'paragraph',
|
||||
attrs: {
|
||||
textAlign: null,
|
||||
class: null
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
@@ -11,11 +11,6 @@
|
||||
}
|
||||
|
||||
let { open = $bindable(false), onDelete = () => {}, isLoading = false }: Props = $props();
|
||||
|
||||
function handleDelete() {
|
||||
onDelete();
|
||||
open = false;
|
||||
}
|
||||
</script>
|
||||
|
||||
<Modal
|
||||
|
||||
@@ -1,17 +1,14 @@
|
||||
<script lang="ts">
|
||||
import { goto } from '$app/navigation';
|
||||
import { browser } from '$app/environment';
|
||||
import BellIcon from '@lucide/svelte/icons/bell';
|
||||
import MoonIcon from '@lucide/svelte/icons/moon';
|
||||
import SunIcon from '@lucide/svelte/icons/sun';
|
||||
import MenuIcon from '@lucide/svelte/icons/menu';
|
||||
import XIcon from '@lucide/svelte/icons/x';
|
||||
import ArrowLeftIcon from '@lucide/svelte/icons/arrow-left';
|
||||
import { ModeSwitcher } from '@cio/ui/base/dark-mode';
|
||||
|
||||
import { IconButton } from '$lib/components/IconButton';
|
||||
import { globalStore } from '$lib/utils/store/app';
|
||||
import { currentOrgPath } from '$lib/utils/store/org';
|
||||
import { toggleBodyByMode } from '$lib/utils/functions/app';
|
||||
import { sideBar } from '../Org/store';
|
||||
import { t } from '$lib/utils/functions/translations';
|
||||
|
||||
@@ -27,16 +24,6 @@
|
||||
$sideBar.hidden = !$sideBar.hidden;
|
||||
};
|
||||
|
||||
function toggleDarkMode() {
|
||||
$globalStore.isDark = !$globalStore.isDark;
|
||||
|
||||
toggleBodyByMode($globalStore.isDark);
|
||||
|
||||
if (browser) {
|
||||
localStorage.setItem('mode', $globalStore.isDark ? 'dark' : '');
|
||||
}
|
||||
}
|
||||
|
||||
let coursesPath = $derived(
|
||||
$globalStore.isOrgSite ? '/lms/mylearning' : isCoursePage ? `${$currentOrgPath}/courses` : $currentOrgPath
|
||||
);
|
||||
@@ -91,13 +78,14 @@
|
||||
<BellIcon class="custom text-white" size={16} />
|
||||
</li>
|
||||
<li>
|
||||
<IconButton size="small" onClick={toggleDarkMode}>
|
||||
<!-- <IconButton size="small" onClick={toggleDarkMode}>
|
||||
{#if $globalStore.isDark}
|
||||
<SunIcon size={16} class="custom" />
|
||||
{:else}
|
||||
<MoonIcon class="custom text-white" size={16} />
|
||||
{/if}
|
||||
</IconButton>
|
||||
</IconButton> -->
|
||||
<ModeSwitcher />
|
||||
</li>
|
||||
|
||||
<li></li>
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
import { isCoursePage } from '$lib/utils/functions/app';
|
||||
import { t } from '$lib/utils/functions/translations';
|
||||
import type { TCustomLinks } from './types';
|
||||
import { ModeSwitcher } from '@cio/ui/base/dark-mode';
|
||||
|
||||
import Logo from './Logo.svelte';
|
||||
import CustomLinks from './CustomLinks.svelte';
|
||||
@@ -76,6 +77,8 @@
|
||||
{:else if !isOrgSite && !$page.url.pathname?.includes('/404')}
|
||||
<AuthButtons {disableSignup} {redirect} />
|
||||
{/if}
|
||||
|
||||
<ModeSwitcher />
|
||||
</ul>
|
||||
|
||||
{#if showLinks}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
<script>
|
||||
import { Popover } from 'carbon-components-svelte';
|
||||
import Menu from './Menu.svelte';
|
||||
import Modal from '$lib/components/Modal/index.svelte';
|
||||
|
||||
import { isMobile } from '$lib/utils/store/useMobile';
|
||||
import { profileMenu } from '../store';
|
||||
import { clickOutside } from '$lib/utils/functions/routes/dashboard';
|
||||
</script>
|
||||
|
||||
{#if $isMobile}
|
||||
@@ -16,15 +17,12 @@
|
||||
>
|
||||
<Menu />
|
||||
</Modal>
|
||||
{:else}
|
||||
<Popover
|
||||
bind:open={$profileMenu.open}
|
||||
align="right-bottom"
|
||||
caret={true}
|
||||
on:click:outside={({ detail }) => {
|
||||
$profileMenu.open = !!$profileMenu.ref?.contains(detail.target);
|
||||
}}
|
||||
{:else if $profileMenu.open}
|
||||
<div
|
||||
use:clickOutside
|
||||
on:click:outside={() => ($profileMenu.open = false)}
|
||||
class="absolute bottom-28 left-0 z-[300] w-full border bg-black p-2 dark:bg-white"
|
||||
>
|
||||
<Menu />
|
||||
</Popover>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
@@ -76,10 +76,11 @@
|
||||
if (error) throw error;
|
||||
} catch (error) {
|
||||
let message = error instanceof Error ? error.message : `${error}`;
|
||||
|
||||
if (message.includes('profile_username_key')) {
|
||||
message = $t('snackbar.lms.error.username_exists');
|
||||
}
|
||||
|
||||
|
||||
snackbar.error(`${$t('snackbar.lms.error.update')}: ${message}`);
|
||||
|
||||
loading = false;
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
import LibraryBigIcon from '@lucide/svelte/icons/library-big';
|
||||
import UsersIcon from '@lucide/svelte/icons/users';
|
||||
import ChevronRightIcon from '@lucide/svelte/icons/chevron-right';
|
||||
import SettingsIcon from '@lucide/svelte/icons/settings';
|
||||
import Settings2 from '@lucide/svelte/icons/settings-2';
|
||||
import MessageSquareMoreIcon from '@lucide/svelte/icons/message-square-more';
|
||||
import BadgeHelpIcon from '@lucide/svelte/icons/badge-help';
|
||||
import Dice6Icon from '@lucide/svelte/icons/dice-6';
|
||||
@@ -98,24 +98,24 @@
|
||||
{#if menuItem.show}
|
||||
<a href="{$currentOrgPath}{menuItem.path}" class="text-black no-underline" onclick={toggleSidebar}>
|
||||
<li
|
||||
class="mb-1 flex items-center gap-2.5 px-2.5 py-2 {NavClasses.item} {menuItem.isActive
|
||||
class="group mb-1 flex items-center gap-2.5 px-2.5 py-2 {NavClasses.item} {menuItem.isActive
|
||||
? NavClasses.active
|
||||
: 'dark:text-white'}"
|
||||
>
|
||||
{#if menuItem.path === ''}
|
||||
<LayoutDashboardIcon size={16} />
|
||||
<LayoutDashboardIcon size={16} class="group-hover:animate-gather" />
|
||||
{:else if menuItem.path === '/courses'}
|
||||
<LibraryBigIcon size={16} />
|
||||
<LibraryBigIcon size={16} class="group-hover:animate-library-expand" />
|
||||
{:else if menuItem.path === '/site'}
|
||||
<LayoutTemplateIcon size={16} />
|
||||
<LayoutTemplateIcon size={16} class="group-hover:animate-template-morph" />
|
||||
{:else if menuItem.path === '/community'}
|
||||
<MessageSquareMoreIcon size={16} />
|
||||
<MessageSquareMoreIcon size={16} class="group-hover:animate-ripple" />
|
||||
{:else if menuItem.path === '/quiz'}
|
||||
<Dice6Icon size={16} />
|
||||
<Dice6Icon size={16} class="group-hover:animate-roll" />
|
||||
{:else if menuItem.path === '/audience'}
|
||||
<UsersIcon size={16} />
|
||||
<UsersIcon size={16} class="group-hover:animate-gather" />
|
||||
{:else if menuItem.path === '/setup'}
|
||||
<SettingsIcon size={16} />
|
||||
<Settings2 size={16} class="group-hover:animate-setup-configure" />
|
||||
{/if}
|
||||
<p class="text-sm font-medium">{menuItem.label}</p>
|
||||
</li>
|
||||
|
||||
@@ -52,3 +52,19 @@ export const formatDate = (_selectedDate: string | undefined): string => {
|
||||
|
||||
return selectedDate.format('ddd, MMM D');
|
||||
};
|
||||
|
||||
export function clickOutside(node) {
|
||||
const handleClick = (event) => {
|
||||
if (node && !node.contains(event.target) && !event.defaultPrevented) {
|
||||
node.dispatchEvent(new CustomEvent('click:outside', { detail: event.target }));
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener('click', handleClick, true);
|
||||
|
||||
return {
|
||||
destroy() {
|
||||
document.removeEventListener('click', handleClick, true);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -158,9 +158,7 @@ export const orgLandingpageValidation = (fields = {}) => {
|
||||
export const onboardingValidation = (fields = {}, step) => {
|
||||
const onboardingValidationSchema = {
|
||||
stepOne: z.object({
|
||||
fullname: z
|
||||
.string()
|
||||
.min(5, { message: `${t.get('validations.onboarding.step_one.full_name.min_char')}` }),
|
||||
fullname: z.string().min(5, { message: `${t.get('validations.onboarding.step_one.full_name.min_char')}` }),
|
||||
orgName: getOrgNameValidation(),
|
||||
siteName: getSiteNameValidation()
|
||||
}),
|
||||
@@ -178,8 +176,7 @@ export const onboardingValidation = (fields = {}, step) => {
|
||||
})
|
||||
};
|
||||
|
||||
const schema =
|
||||
step === 1 ? onboardingValidationSchema.stepOne : onboardingValidationSchema.stepTwo;
|
||||
const schema = step === 1 ? onboardingValidationSchema.stepOne : onboardingValidationSchema.stepTwo;
|
||||
const { error } = schema.safeParse(fields);
|
||||
|
||||
return processErrors(error);
|
||||
|
||||
@@ -13,7 +13,13 @@
|
||||
// import PageLoadProgressBar from '$lib/components/Progress/PageLoadProgressBar.svelte';
|
||||
import Snackbar from '$lib/components/Snackbar/index.svelte';
|
||||
import UpgradeModal from '$lib/components/Upgrade/Modal.svelte';
|
||||
import { isCoursesPage, isLMSPage, isOrgPage, toggleBodyByMode } from '$lib/utils/functions/app';
|
||||
import {
|
||||
isCoursesPage,
|
||||
isLMSPage,
|
||||
isOrgPage
|
||||
|
||||
// toggleBodyByMode
|
||||
} from '$lib/utils/functions/app';
|
||||
import { getProfile, setupAnalytics } from '$lib/utils/functions/appSetup';
|
||||
import hideNavByRoute from '$lib/utils/functions/routes/hideNavByRoute';
|
||||
import { getSupabase } from '$lib/utils/functions/supabase';
|
||||
@@ -28,7 +34,9 @@
|
||||
import { onMount } from 'svelte';
|
||||
import { MetaTags } from 'svelte-meta-tags';
|
||||
|
||||
import '../app.postcss';
|
||||
import { ModeWatcher } from '@cio/ui/base/dark-mode';
|
||||
|
||||
import '../app.css';
|
||||
|
||||
let { data, children } = $props();
|
||||
|
||||
@@ -52,8 +60,8 @@
|
||||
|
||||
if (browser) {
|
||||
// Update theme - dark or light mode
|
||||
$globalStore.isDark = localStorage.getItem('mode') === 'dark';
|
||||
toggleBodyByMode($globalStore.isDark);
|
||||
// $globalStore.isDark = localStorage.getItem('mode') === 'dark';
|
||||
// toggleBodyByMode($globalStore.isDark);
|
||||
|
||||
if (data.isOrgSite && data.org?.theme) {
|
||||
setTheme(data.org?.theme);
|
||||
@@ -116,9 +124,11 @@
|
||||
|
||||
<svelte:window onresize={handleResize} />
|
||||
|
||||
<ModeWatcher />
|
||||
|
||||
<MetaTags {...metaTags} />
|
||||
|
||||
<Theme bind:theme={carbonTheme} />
|
||||
<!-- <Theme bind:theme={carbonTheme} /> -->
|
||||
|
||||
<UpgradeModal />
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
import PrimaryButton from '$lib/components/PrimaryButton/index.svelte';
|
||||
import { getSupabase } from '$lib/utils/functions/supabase';
|
||||
import { validateEmail } from '$lib/utils/functions/validateEmail';
|
||||
import Edra from '$lib/components/Edra/EdraRoot.svelte';
|
||||
|
||||
let email = $state('');
|
||||
let isAdding = $state(false);
|
||||
@@ -57,7 +58,7 @@
|
||||
<title>ClassroomIO - The classroom software</title>
|
||||
</svelte:head>
|
||||
|
||||
<div class="m-2 flex w-screen flex-col items-center justify-center font-sans sm:m-0 md:h-[93vh]">
|
||||
<div class="m-2 flex w-screen flex-col items-center justify-center font-sans sm:m-0 md:min-h-[93vh] dark:bg-black">
|
||||
<!--
|
||||
<img
|
||||
src="/logo-192.png"
|
||||
@@ -73,8 +74,9 @@
|
||||
<p class="text-center text-lg dark:text-white">The operating system for classroooms of the future 🚀🚀🚀.</p>
|
||||
</div>
|
||||
|
||||
<Edra />
|
||||
<form onsubmit={preventDefault(handleSubmit)} class="my-4 hidden">
|
||||
<div class="flex flex-col items-center sm:flex-row">
|
||||
<div class="flex hidden flex-col items-center sm:flex-row">
|
||||
{#if success}
|
||||
<p class="dark:text-white">You have been added successfully. Thanks for joining.</p>
|
||||
{:else}
|
||||
@@ -91,18 +93,18 @@
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div class="mt-4 flex flex-col md:flex-row">
|
||||
<div class="mt-4 flex md:flex-row">
|
||||
{#each areas as area, index}
|
||||
<div
|
||||
class="active hover:border-primary-700 m-3 max-w-[350px] rounded-md border-2 bg-white px-12 py-3 shadow-xl dark:bg-black {animate}"
|
||||
>
|
||||
<h3 class="text-3xl dark:text-white">
|
||||
{#if index === 0}
|
||||
<DoorOpenIcon size={16} />
|
||||
<DoorOpenIcon size={30} />
|
||||
{:else if index === 1}
|
||||
<ChartAreaIcon size={16} />
|
||||
<ChartAreaIcon size={30} />
|
||||
{:else if index === 2}
|
||||
<BotIcon size={16} />
|
||||
<BotIcon size={30} />
|
||||
{/if}
|
||||
{area.title}
|
||||
</h3>
|
||||
|
||||
@@ -29,8 +29,8 @@
|
||||
import { LANGUAGES } from '$lib/utils/constants/translation';
|
||||
import { t } from '$lib/utils/functions/translations';
|
||||
import { profile } from '$lib/utils/store/user';
|
||||
import { COURSE_VERSION, type Lesson, type LessonCompletion } from '$lib/utils/types';
|
||||
import { Dropdown } from 'carbon-components-svelte';
|
||||
import { COURSE_VERSION, LOCALE, type Lesson, type LessonCompletion } from '$lib/utils/types';
|
||||
import * as DropdownMenu from '@cio/ui/base/dropdown-menu';
|
||||
|
||||
let { data = $bindable() } = $props();
|
||||
|
||||
@@ -248,7 +248,20 @@
|
||||
</IconButton>
|
||||
</div>
|
||||
|
||||
<Dropdown items={LANGUAGES} bind:selectedId={$lesson.locale} class="h-full" />
|
||||
<div>
|
||||
<DropdownMenu.Root>
|
||||
<DropdownMenu.Trigger>Open</DropdownMenu.Trigger>
|
||||
<DropdownMenu.Content>
|
||||
<DropdownMenu.Group>
|
||||
{#each LANGUAGES as lang}
|
||||
<DropdownMenu.Item onclick={() => ($lesson.locale = lang.id as LOCALE)}>
|
||||
{lang.text}
|
||||
</DropdownMenu.Item>
|
||||
{/each}
|
||||
</DropdownMenu.Group>
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Root>
|
||||
</div>
|
||||
{/if}
|
||||
</RoleBasedSecurity>
|
||||
</div>
|
||||
|
||||
@@ -137,7 +137,7 @@
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 gap-4 lg:grid-cols-2">
|
||||
<div class="flex min-h-[45vh] w-full flex-col rounded-md border p-3 md:p-5 dark:border-neutral-600">
|
||||
<div class="flex min-h-[45vh] w-full flex-col rounded-md border p-3 md:p-5 dark:border-neutral-600 dark:text-white">
|
||||
<h3 class="mt-0 text-2xl font-bold">
|
||||
{$t('dashboard.top_courses')}
|
||||
</h3>
|
||||
@@ -189,7 +189,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex min-h-[45vh] w-full flex-col rounded-md border p-3 md:p-5 dark:border-neutral-600">
|
||||
<div class="flex min-h-[45vh] w-full flex-col rounded-md border p-3 md:p-5 dark:border-neutral-600 dark:text-white">
|
||||
<h3 class="mt-0 text-2xl font-bold">
|
||||
{$t('dashboard.recent_enrollments')}
|
||||
</h3>
|
||||
|
||||
@@ -12,12 +12,17 @@ const config = {
|
||||
// Consult https://kit.svelte.dev/docs/integrations#preprocessors
|
||||
// for more information about preprocessors
|
||||
preprocess: [vitePreprocess({})],
|
||||
|
||||
kit: {
|
||||
adapter: useNodeAdapter ? adapterNode() : adapterVercel(),
|
||||
alias: {
|
||||
$lib: path.resolve('./src/lib'),
|
||||
$mail: path.resolve('./src/mail')
|
||||
$mail: path.resolve('./src/mail'),
|
||||
'$src/tools': path.resolve('../../packages/ui/src/tools/index.ts'),
|
||||
'$src/base/*': path.resolve('../../packages/ui/src/base/*'),
|
||||
'@cio/ui': path.resolve('../../packages/ui/src'),
|
||||
'@cio/ui/*': path.resolve('../../packages/ui/src/*'),
|
||||
'@cio/api': path.resolve('../api/dist'),
|
||||
'@cio/api/*': path.resolve('../api/dist/*')
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
export default {
|
||||
darkMode: 'class',
|
||||
content: ['./src/**/*.{html,js,svelte,ts}'],
|
||||
theme: {
|
||||
extend: {}
|
||||
},
|
||||
plugins: [require('@tailwindcss/typography'), require('@tailwindcss/forms')]
|
||||
};
|
||||
@@ -1,7 +1,6 @@
|
||||
import { defineConfig, loadEnv } from 'vite';
|
||||
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { sveltekit } from '@sveltejs/kit/vite';
|
||||
|
||||
export default ({ mode }) => {
|
||||
@@ -25,15 +24,12 @@ export default ({ mode }) => {
|
||||
include: ['@cio/api/rpc-types']
|
||||
},
|
||||
resolve: {
|
||||
mainFields: ['browser'],
|
||||
alias: {
|
||||
'@cio/api': path.resolve(__dirname, '../api/dist')
|
||||
}
|
||||
mainFields: ['browser']
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
function getServer(params: any) {
|
||||
function getServer(params) {
|
||||
const { VITE_USE_HTTPS_ON_LOCALHOST } = params || {};
|
||||
if (VITE_USE_HTTPS_ON_LOCALHOST === 'true') {
|
||||
return {
|
||||
|
||||
Reference in New Issue
Block a user