├── src ├── lib │ ├── config.ts │ ├── student.ts │ ├── utils.ts │ ├── competencies.ts │ ├── filter.ts │ └── parser.ts ├── vite-env.d.ts ├── main.ts ├── components │ ├── Counter.svelte │ ├── StudentBanner.svelte │ ├── ExpectationLegend.svelte │ └── Icon.svelte ├── routes.ts ├── pages │ ├── NotFound.svelte │ ├── Chart.svelte │ ├── ImportMyCompetencies.svelte │ ├── List.svelte │ └── Home.svelte ├── assets │ ├── github.svg │ └── svelte.svg ├── store.ts ├── app.scss └── App.svelte ├── public ├── robots.txt └── favicon-32x32.png ├── .vscode └── extensions.json ├── tsconfig.node.json ├── svelte.config.js ├── vite.config.ts ├── .gitignore ├── index.html ├── tsconfig.json ├── package.json ├── LICENSE ├── nginx └── default.conf ├── .github └── workflows │ └── pages.yml ├── README.md ├── helpers └── competencyApi.js └── yarn.lock /src/lib/config.ts: -------------------------------------------------------------------------------- 1 | export const pageTransitionDuration = 200 2 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: / -------------------------------------------------------------------------------- /src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | -------------------------------------------------------------------------------- /public/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GreenDjango/msc-competency-framework/HEAD/public/favicon-32x32.png -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "svelte.svelte-vscode", 4 | "esbenp.prettier-vscode" 5 | ] 6 | } -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import App from './App.svelte' 2 | import './app.scss' 3 | 4 | const app = new App({ 5 | target: document.getElementById('app')!, 6 | }) 7 | 8 | export default app 9 | -------------------------------------------------------------------------------- /tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "skipLibCheck": true, 5 | "module": "ESNext", 6 | "moduleResolution": "bundler" 7 | }, 8 | "include": ["vite.config.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /src/components/Counter.svelte: -------------------------------------------------------------------------------- 1 | 7 | 8 | 11 | -------------------------------------------------------------------------------- /svelte.config.js: -------------------------------------------------------------------------------- 1 | import { vitePreprocess } from '@sveltejs/vite-plugin-svelte' 2 | 3 | export default { 4 | // Consult https://svelte.dev/docs#compile-time-svelte-preprocess 5 | // for more information about preprocessors 6 | preprocess: vitePreprocess(), 7 | } 8 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import { svelte } from '@sveltejs/vite-plugin-svelte' 2 | import { defineConfig } from 'vite' 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [svelte()], 7 | json: { 8 | stringify: true, 9 | }, 10 | }) 11 | -------------------------------------------------------------------------------- /src/lib/student.ts: -------------------------------------------------------------------------------- 1 | import { derived } from 'svelte/store' 2 | 3 | import { myBehaviorsStore, studentInfoStore } from '../store' 4 | 5 | export function signOut() { 6 | studentInfoStore.reset() 7 | myBehaviorsStore.reset() 8 | } 9 | 10 | export const isSignIn = derived(studentInfoStore, ($student) => !!$student?.email) 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | Competency Framework 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/routes.ts: -------------------------------------------------------------------------------- 1 | import Chart from './pages/Chart.svelte' 2 | import Home from './pages/Home.svelte' 3 | import ImportMyCompetencies from './pages/ImportMyCompetencies.svelte' 4 | import List from './pages/List.svelte' 5 | import NotFound from './pages/NotFound.svelte' 6 | 7 | export default { 8 | '/': Home, 9 | 10 | '/list': List, 11 | 12 | '/chart': Chart, 13 | 14 | '/my': ImportMyCompetencies, 15 | 16 | // '/chart': wrap({ 17 | // asyncComponent: () => import('./pages/Chart.svelte'), 18 | // loadingComponent: Loading, 19 | // loadingParams: { 20 | // message: 'Loading the Name route…', 21 | // }, 22 | // }), 23 | 24 | '*': NotFound, 25 | } 26 | -------------------------------------------------------------------------------- /src/pages/NotFound.svelte: -------------------------------------------------------------------------------- 1 | 7 | 8 |
9 |

Not Found

10 |

Oops, this route doesn't exist!

11 | Go to home 12 |
13 | 14 | 28 | -------------------------------------------------------------------------------- /src/components/StudentBanner.svelte: -------------------------------------------------------------------------------- 1 | 4 | 5 |
6 | {#if $studentInfoStore} 7 |
8 |
{$studentInfoStore.email}
9 |
{$studentInfoStore.cursus} / {$studentInfoStore.trainingPath}
10 |
Promo {$studentInfoStore.promotion}
11 |
{$studentInfoStore.campus} - {$studentInfoStore.resp}
12 |
13 | {/if} 14 |
15 | 16 | 30 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@tsconfig/svelte/tsconfig.json", 3 | "compilerOptions": { 4 | "target": "ESNext", 5 | "useDefineForClassFields": true, 6 | "module": "ESNext", 7 | "resolveJsonModule": true, 8 | /** 9 | * Typecheck JS in `.svelte` and `.js` files by default. 10 | * Disable checkJs if you'd like to use dynamic types in JS. 11 | * Note that setting allowJs false does not prevent the use 12 | * of JS in `.svelte` files. 13 | */ 14 | "allowJs": true, 15 | "checkJs": true, 16 | "isolatedModules": true, 17 | "strict": true, 18 | "noImplicitAny": true, 19 | "noImplicitReturns": true, 20 | "noImplicitThis": true, 21 | "strictNullChecks": true 22 | }, 23 | "include": ["src/**/*.ts", "src/**/*.js", "src/**/*.svelte"], 24 | "references": [{ "path": "./tsconfig.node.json" }] 25 | } 26 | -------------------------------------------------------------------------------- /src/assets/github.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "competency-framework", 3 | "private": true, 4 | "version": "1.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "vite build", 9 | "preview": "vite preview", 10 | "check": "svelte-check --tsconfig ./tsconfig.json", 11 | "prettier:diff": "prettier --check src", 12 | "generate:competencies": "node ./helpers/competencyApi.js --compact" 13 | }, 14 | "dependencies": { 15 | "axios": "^1.3.3", 16 | "js-base64": "^3.7.2", 17 | "sass": "^1.54.0", 18 | "svelte": "^4.2.8", 19 | "svelte-spa-router": "^4.0.0" 20 | }, 21 | "devDependencies": { 22 | "@sveltejs/vite-plugin-svelte": "^3.0.1", 23 | "@tsconfig/svelte": "^5.0.2", 24 | "prettier": "^3.1.1", 25 | "prettier-plugin-svelte": "^3.1.2", 26 | "svelte-check": "^3.6.2", 27 | "tslib": "^2.6.2", 28 | "typescript": "^5.2.2", 29 | "vite": "^5.0.8" 30 | }, 31 | "prettier": { 32 | "trailingComma": "es5", 33 | "useTabs": false, 34 | "tabWidth": 2, 35 | "singleQuote": true, 36 | "semi": false, 37 | "printWidth": 90 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Théo 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/lib/utils.ts: -------------------------------------------------------------------------------- 1 | import { decode, encode } from 'js-base64' 2 | 3 | export function findId( 4 | array: readonly T[] | undefined, 5 | id: ID 6 | ) { 7 | return array?.find((val) => val.id === id) 8 | } 9 | 10 | /** Only for JSON data safe */ 11 | export function deepClone(data: T) { 12 | return JSON.parse(JSON.stringify(data)) as T 13 | } 14 | 15 | export function cleanDOMString(str: string) { 16 | return str.replaceAll('\n', '').replaceAll(/\s+/g, ' ').trim() 17 | } 18 | 19 | export function storeBase64(key: string, value: any) { 20 | const encodedValue = encode(JSON.stringify(value)) 21 | globalThis.localStorage.setItem(key, encodedValue) 22 | } 23 | 24 | export function loadBase64(key: string) { 25 | const rawValue = globalThis.localStorage.getItem(key) 26 | 27 | if (!rawValue) return null 28 | try { 29 | return JSON.parse(decode(rawValue)) 30 | } catch (err) { 31 | console.error(`loadBase64: value=${rawValue}`, err) 32 | return null 33 | } 34 | } 35 | 36 | export function removeLocalStorageItem(key: string) { 37 | globalThis.localStorage.removeItem(key) 38 | } 39 | -------------------------------------------------------------------------------- /nginx/default.conf: -------------------------------------------------------------------------------- 1 | http { 2 | include mime.types; 3 | default_type application/octet-stream; 4 | sendfile on; 5 | keepalive_timeout 65; 6 | 7 | server { 8 | listen 80; 9 | listen [::]:80; 10 | server_name _; 11 | 12 | gzip on; 13 | gzip_comp_level 6; # default 1 (1-9) 14 | gzip_min_length 256; # default 20 15 | gzip_types text/plain text/css application/json application/javascript application/x-javascript text/xml application/xml application/xml+rss application/rss+xml text/javascript application/x-font-ttf font/opentype image/jpeg image/png image/svg+xml image/x-icon; 16 | 17 | location / { 18 | root /data; 19 | index index.html index.htm; 20 | try_files $uri $uri/ /index.html; 21 | } 22 | 23 | location ~* \.(?:ico|css|js|gif|jpe?g|png|svg|otf|ttf|eot|woff|woff2)$ { 24 | root /data; 25 | expires 90d; 26 | access_log off; 27 | } 28 | 29 | error_page 500 502 503 504 /50x.html; 30 | location = /50x.html { 31 | root html; 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/store.ts: -------------------------------------------------------------------------------- 1 | import { get, writable } from 'svelte/store' 2 | 3 | import type { 4 | ImportInfo, 5 | MyBehavior, 6 | StudentInfo, 7 | TrainingPath, 8 | } from './lib/competencies' 9 | import { loadBase64, removeLocalStorageItem, storeBase64 } from './lib/utils' 10 | 11 | function persistentStoreFactory(storeKey: string) { 12 | const { subscribe, set, update } = writable(null, (set) => { 13 | const data = loadBase64(storeKey) 14 | set(data) 15 | }) 16 | 17 | return { 18 | subscribe, 19 | set: (value: T) => { 20 | set(value) 21 | storeBase64(storeKey, value) 22 | }, 23 | update: (updater: (value: T | null) => T) => { 24 | update(updater) 25 | storeBase64(storeKey, get({ subscribe })) 26 | }, 27 | reset: () => { 28 | set(null) 29 | removeLocalStorageItem(storeKey) 30 | }, 31 | } 32 | } 33 | 34 | type Preference = { speId?: TrainingPath; projectId?: string } 35 | export const preferenceStore = persistentStoreFactory('preference') 36 | 37 | export const myBehaviorsStore = persistentStoreFactory('my_behaviors') 38 | 39 | export const studentInfoStore = persistentStoreFactory('student') 40 | 41 | export const lastImportInfoStore = persistentStoreFactory('last_import_info') 42 | -------------------------------------------------------------------------------- /src/pages/Chart.svelte: -------------------------------------------------------------------------------- 1 | 6 | 7 |
8 |
9 |