feat: [lw] free tools (#297)
* checkpoint * checkpoint * feat: Create free tools page * responsiveness fix * rename free-tools to tools * add slug * feat: Random name picker * quick fix * PR Fixes * PR Fixes * - adjustments * update env example * remove base url * remove base url in .env.example * fix type errors * add og images * add signup cta to /tools layout * update cta * add socials avatar * add meta tags * feat: tic tac toe (#307) * Feat: tic tac toe * PR Adjustments * feat: Implement Timed Activity Tool (#316) * chore: update verification email content * chore: landing page improvements * feat: grade exercise with AI (#296) * working on the UI of the grading * working on the grading function of the radio and checkboxes * ai grading fully implemented with dummy functions for generating scores * working on the openai response * working on the openai response * finished with the ai_grading * finished with the ai_grading * creating new migration file * working on reviews * updated the translations * improvements --------- Co-authored-by: Rotimi Best <[email protected]> * feat: selfhost tinymce * fix: logo resolution error in manifest.json * feat: move from gpt3.5 to gpt-4o * feat: customize the LMS from dashboard (#304) * Done with the customization of the LMS task * generated custom_lms migration file * updated the migration file * copy improvements * update wording * made updates based on reviews,i hope i didnt miss anything * updated the translations and migration files * refactoring * disabled improvements for apps * more improvements * more custom improvements * pr refactoring --------- Co-authored-by: Rotimi Best <[email protected]> * fix: CustomizeLMS not found * fix: CustomizeLms wrong path * setup * docs: update short hero tagline * debug: courses not showing on vercel * more debugging * i think this fixes it * fix: phew, clean up debug cause it works * fix: move RBS into coursecontainer on marks page * chore: cleaner way to implement redirect if role permission denied * docs: update tagline * fix: delete /org/# route * checkpoint * add reusable header component * fix: empty lesson title bug (#314) * finished the lesson title bug * made chnages based on review * Update +page.svelte --------- Co-authored-by: Rotimi Best <[email protected]> * feat: add sound cdn * remove console.log * feat: course types - self paced and live class (#309) * working on the course type * trying to mark it persist * almost done here * fixed sendig emtpy options bug * created migration file * created migration file * made updates based on PR review * merge * update course type card * add redirect logic with classroomio.com/new * make new course modal only open/close with url * feat: page active state (#315) * finished the active link state * updated the active link state based on review * simplify code --------- Co-authored-by: Rotimi Best <[email protected]> * add type to course card --------- Co-authored-by: Rotimi Best <[email protected]> * general improvements to free tools * fix timer and styling issues --------- Co-authored-by: Rotimi Best <[email protected]> Co-authored-by: Em_Dev <[email protected]> * update tagline for tools page * feat: Add Free Section to Landing Page (#332) * feat: Add Free Section to Landing Page * PR Fixes * refactor * PR Fixes * restructure * improve cursor UX --------- Co-authored-by: Rotimi Best <[email protected]> * feat: Progress Report Tool (#329) * checkpoint * fix: corrections * finalization * improve ux of progress tool * more styling improvements * PR Fixes * PR Fixes * fix: mood bg on report * fix: generate exercise with AI bug (#331) * fix: enable lesson cascade delete (#335) * styling improvements * chore: hide course grading toggle in settings (#338) * feat: reset select in community page (#339) * Feat: Add Reset for comminity dropdown * - add translation * refactor --------- Co-authored-by: Rotimi Best <[email protected]> * fix: prevent hypen at start or end of organzation or domain name (#340) * fix: adding hypen to start or end of org or domain name * fix: add org name validation to settings page * chore: remove unnecessary variable use * chore: remove error reset function * Update OrgSettings.svelte --------- Co-authored-by: Rotimi Best <[email protected]> * fix: full screen * fix: pnpm version error & add top level .nvmrc (#343) * update name of tool * chore: add translations for validation messages (#345) * chore: add validation texts to locale file * feat: switch all validations from plain text to use i18n locale * feat: switch review form validations from plain text to use locale file * run translation script * chore: add translations for validation messages (#345) (#347) * chore: add translations for validation messages (#345) * chore: add validation texts to locale file * feat: switch all validations from plain text to use i18n locale * feat: switch review form validations from plain text to use locale file * run translation script --------- Co-authored-by: Iniubong Obonguko <[email protected]> * PR Fixes * Bug fixes on mobile * Final bug fix * code improvements * PR Fixes * Merge Fix * PR Fixes * fix: width * code cleanup * feat: Promodo Tool (tracks tim spent) * feat: Pomodoro timer (without tracking feature) * fix: - change pause functionality to next - correct spelling of pomodoro * fix: pomodoro logo rename * styling improvements --------- Co-authored-by: Rotimi Best <[email protected]> Co-authored-by: Em_Dev <[email protected]> Co-authored-by: Iniubong Obonguko <[email protected]> Co-authored-by: Iniubong Obonguko <[email protected]> Co-authored-by: Aditya Deshlahre <[email protected]> * feat: Implement Bottom menu (#341) * popup logic done * popup UI first phase done * finished the menupop up for both lms and admin * finished the menupop up for both lms and admin * done * cleanup * fixed popover position * still working on the routing * i think that is it for the day * updated the menupopup currentorgdomain * rename popup to bottomMenu * refactoring --------- Co-authored-by: Rotimi Best <[email protected]> * remove logout * change tagline * feat: Store todolist to localStorage (#375) * feat: Save todo to localStorage * remove fly animation to todos * feat: Progress Report avatar update (#376) * feat: Reorder Avatar list * quick fix * change avatar url * more improvements * quick background fix * fix: white background * body background fix * update lock --------- Co-authored-by: Rotimi Best <[email protected]> Co-authored-by: Em_Dev <[email protected]> Co-authored-by: Iniubong Obonguko <[email protected]> Co-authored-by: Iniubong Obonguko <[email protected]> Co-authored-by: Aditya Deshlahre <[email protected]>
This commit is contained in:
co-authored by
Rotimi Best
Em_Dev
Iniubong Obonguko
Iniubong Obonguko
Aditya Deshlahre
parent
91bd9d4414
commit
9c066c79be
@@ -0,0 +1,426 @@
|
||||
<script lang="ts">
|
||||
import { LogoFacebook, LogoLinkedin } from 'carbon-icons-svelte';
|
||||
import { onDestroy } from 'svelte';
|
||||
import { fly } from 'svelte/transition';
|
||||
import { sineInOut } from 'svelte/easing';
|
||||
import Mood from './components/Mood.svelte';
|
||||
import ToolsHeader from '$lib/ToolsHeader/ToolsHeader.svelte';
|
||||
import Avatar from './components/Avatar.svelte';
|
||||
import Background from './components/Background.svelte';
|
||||
import Report from './components/Report.svelte';
|
||||
import FullView from './components/FullView.svelte';
|
||||
import DownloadButton from './components/DownloadButton.svelte';
|
||||
import { htmlBody, type HtmlBody, openModal, isFormComplete } from './components/store';
|
||||
|
||||
const MAX_CHARS = 160;
|
||||
let remainingChars = MAX_CHARS;
|
||||
let showReport: boolean = false;
|
||||
let showSetter: boolean = true;
|
||||
let isDownloading: boolean = false;
|
||||
let isDisabled: boolean;
|
||||
|
||||
// sets the result of the mini validation for the htmlBody store
|
||||
const unsubscribe = isFormComplete.subscribe((value) => {
|
||||
isDisabled = value;
|
||||
});
|
||||
|
||||
function handleInputChange(event: any, property: keyof HtmlBody) {
|
||||
$htmlBody[property] = event.target.value;
|
||||
|
||||
if (property === 'learning') {
|
||||
remainingChars = MAX_CHARS - event.target.value.length;
|
||||
}
|
||||
}
|
||||
|
||||
function shareTemplate() {
|
||||
return encodeURIComponent(
|
||||
`Learning Progress Update: ${$htmlBody.learning} and I'm ${$htmlBody.mood.text}. \nGenerated yours on classroomio.com\n`
|
||||
);
|
||||
}
|
||||
|
||||
function shareOnTwitter() {
|
||||
const text = shareTemplate();
|
||||
const hashtags = encodeURIComponent('classroomIO,progressReport');
|
||||
const url = `https://x.com/intent/post?text=${text}&hashtags=${hashtags}`;
|
||||
window.open(url);
|
||||
}
|
||||
|
||||
function shareOnFacebook() {
|
||||
const text = shareTemplate();
|
||||
window.open(
|
||||
`https://www.facebook.com/dialog/share?app_id=145634995501895&display=popup&href=https%3A%2F%2Fclassroomio.com%2Fdocs%2F&redirect_uri=https%3A%2F%2Fclassroomio.com%2Ftools%2Fexplorer&hashtag=${text}`
|
||||
);
|
||||
}
|
||||
|
||||
function shareOnLinkedIn() {
|
||||
const text = shareTemplate();
|
||||
const url = `http://www.linkedin.com/shareArticle?summary=${text}&text=${text}`;
|
||||
window.open(url);
|
||||
}
|
||||
|
||||
onDestroy(() => {
|
||||
unsubscribe();
|
||||
});
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Random Name Picker | ClassroomIO</title>
|
||||
<meta
|
||||
property="og:image"
|
||||
itemprop="image"
|
||||
content="https://brand.cdn.clsrio.com/og/free-tools.png"
|
||||
/>
|
||||
<meta property="og:title" content="Random Name Picker | ClassroomIO" />
|
||||
<meta
|
||||
property="og:description"
|
||||
content="Use this online name picker to draw a random name from a list of names for your online or physical classroom."
|
||||
/>
|
||||
|
||||
<meta
|
||||
property="og:image:secure_url"
|
||||
itemprop="image"
|
||||
content="https://brand.cdn.clsrio.com/og/free-tools.png"
|
||||
/>
|
||||
|
||||
<meta name="twitter:title" content="Random Name Picker | ClassroomIO" />
|
||||
<meta
|
||||
name="twitter:description"
|
||||
content="Use this online name picker to draw a random name from a list of names for your online or physical classroom."
|
||||
/>
|
||||
<meta name="twitter:image" content="https://brand.cdn.clsrio.com/og/free-tools.png" />
|
||||
</svelte:head>
|
||||
|
||||
<section class="mt-[10%] md:mt-16 px-1 md:px-0 w-full md:w-full">
|
||||
<ToolsHeader>
|
||||
<img
|
||||
src="/free-tools/progress-report.svg"
|
||||
class="w-[15%] md:w-[5%] mx-auto border rounded-full"
|
||||
alt=""
|
||||
/>
|
||||
<h1 class="text-4xl md:text-6xl font-bold text-[#040F2D] my-3">Progress Report</h1>
|
||||
<p class="text-md text-[#656565] font-light md:font-normal md:w-[45%] mx-auto">
|
||||
Generate cool reports of your learning progress. Share reports with your network for
|
||||
collaborative learning
|
||||
</p>
|
||||
</ToolsHeader>
|
||||
|
||||
<!-- modals -->
|
||||
<Avatar />
|
||||
<Mood />
|
||||
<Background />
|
||||
<FullView />
|
||||
|
||||
<div
|
||||
class="border rounded-md w-full md:w-11/12 lg:w-[80%] my-10 mx-auto shadow-md flex md:flex-row flex-col justify-evenly"
|
||||
>
|
||||
<!-- left side -->
|
||||
{#if showSetter}
|
||||
<div
|
||||
transition:fly={{ y: 100, easing: sineInOut }}
|
||||
class="w-full mx-auto md:w-[48%] md:max-w-[500px] p-5 md:border-r"
|
||||
>
|
||||
<!-- preview button -->
|
||||
<div class="flex md:hidden justify-end mb-5">
|
||||
<button
|
||||
type="button"
|
||||
on:click={() => {
|
||||
showSetter = false;
|
||||
showReport = true;
|
||||
}}
|
||||
class="flex justify-between items-center gap-2 px-3 py-1 rounded-full text-white text-xs bg-[#0233BD]"
|
||||
>
|
||||
<img
|
||||
src="/free-tools/progress-report/preview-icon.svg"
|
||||
alt="preview icon"
|
||||
class="w-4"
|
||||
/>
|
||||
Preview
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- name input -->
|
||||
<div>
|
||||
<p class="text-sm text-[#656565]">Add your name</p>
|
||||
<input
|
||||
type="text"
|
||||
bind:value={$htmlBody.name}
|
||||
on:input={(event) => handleInputChange(event, 'name')}
|
||||
placeholder="Enter your name here"
|
||||
class="w-full border my-3 py-2 px-3 outline-none rounded-sm bg-[#F1F2F4] text-xs placeholder:text-sm placeholder:text-[#ADADAD]"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- mood input -->
|
||||
<div class="mt-3">
|
||||
<p class="text-sm text-[#656565]">Select your mood</p>
|
||||
<button
|
||||
type="button"
|
||||
on:click={() => ($openModal.mood = true)}
|
||||
class="w-full flex justify-between items-center border my-3 py-2 px-3 outline-none rounded-sm bg-[#F1F2F4] text-gray-400 text-sm"
|
||||
>
|
||||
{#if $htmlBody.mood.text}
|
||||
<div
|
||||
class="px-3 py-1 border border-[#EDEDED] text-white bg-blue-500 rounded-2xl font-semibold text-[7px] md:text-xs flex items-center justify-between"
|
||||
>
|
||||
{#if $htmlBody.mood.text}
|
||||
<span class="flex gap-1 items-center">
|
||||
<p class="font-semibold text-[9px] md:text-xs">is {$htmlBody.mood.text}</p>
|
||||
<img
|
||||
src="https://assets.cdn.clsrio.com/progress-report/emojis/{$htmlBody.mood
|
||||
.iconSrc}.png"
|
||||
alt={$htmlBody.mood.text}
|
||||
class="w-3"
|
||||
/>
|
||||
</span>
|
||||
{/if}
|
||||
</div>
|
||||
{:else}
|
||||
What feeling describes you?
|
||||
<img
|
||||
src="/free-tools/progress-report/smiley-face-icon.svg"
|
||||
alt="smiley face"
|
||||
class="w-3"
|
||||
/>
|
||||
{/if}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- learning input -->
|
||||
<div class="mt-5">
|
||||
<p class="text-sm text-[#656565]">What are you learning?</p>
|
||||
<textarea
|
||||
bind:value={$htmlBody.learning}
|
||||
maxlength={MAX_CHARS}
|
||||
on:input={(event) => handleInputChange(event, 'learning')}
|
||||
placeholder="Tell us what you are learning"
|
||||
class="w-full h-[17vh] border my-3 py-2 px-3 outline-none rounded-sm bg-[#F1F2F4] text-sm placeholder:text-sm placeholder:text-[#ADADAD]"
|
||||
></textarea>
|
||||
<p class="text-xs text-right text-[#656565]">{remainingChars} characters remaining</p>
|
||||
</div>
|
||||
|
||||
<!-- range input -->
|
||||
<div class="mt-3">
|
||||
<p class="text-sm text-[#656565] pb-4">Estimate your progress</p>
|
||||
<div class="flex justify-between items-center">
|
||||
<input
|
||||
type="range"
|
||||
min="0"
|
||||
max="100"
|
||||
bind:value={$htmlBody.progress}
|
||||
class="range-input"
|
||||
on:input={(event) => handleInputChange(event, 'progress')}
|
||||
style="background: linear-gradient(to right, #0F62FE {$htmlBody.progress}%, #ccc {$htmlBody.progress}%);"
|
||||
/>
|
||||
<p class="text-sm font-semibold">{$htmlBody.progress}%</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- avatar button -->
|
||||
<div class="flex flex-wrap justify-between items-center mt-3">
|
||||
<div class="w-full md:w-2/4">
|
||||
<p class="text-[12px] md:text-sm text-[#656565]">Choose your avatar</p>
|
||||
<button
|
||||
type="button"
|
||||
on:click={() => ($openModal.avatar = true)}
|
||||
class="bg-[#F7F7F7] w-full py-1.5 px-5 md:px-0 mt-3 flex md:w-[65%] items-center justify-between md:justify-center gap-2"
|
||||
>
|
||||
<img
|
||||
src={`https://assets.cdn.clsrio.com/progress-report/avatars/${
|
||||
$htmlBody.avatar || 'avatar_m'
|
||||
}.svg`}
|
||||
alt=""
|
||||
class="w-[15%] md:w-[30%]"
|
||||
/>
|
||||
<img
|
||||
src="/free-tools/progress-report/question-mark.svg"
|
||||
alt="A question mark"
|
||||
class="w-[15%] md:w-[30%]"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- & background -->
|
||||
<div class="w-full md:w-2/4">
|
||||
<p class="text-[12px] md:text-sm text-[#656565]">Choose your background</p>
|
||||
<button
|
||||
type="button"
|
||||
on:click={() => ($openModal.background = true)}
|
||||
class="bg-[#F7F7F7] w-full py-1.5 px-5 md:px-0 mt-3 flex md:w-[65%] items-center justify-between md:justify-center gap-2"
|
||||
>
|
||||
<img
|
||||
src={`https://assets.cdn.clsrio.com/progress-report/backgrounds/${
|
||||
$htmlBody.background || 'blue_tetiary_background'
|
||||
}.webp`}
|
||||
alt="An avatar"
|
||||
class="w-[15%] md:w-[30%]"
|
||||
/>
|
||||
<img
|
||||
src="/free-tools/progress-report/question-mark.svg"
|
||||
alt="A question mark"
|
||||
class="w-[15%] md:w-[30%]"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<DownloadButton {isDisabled} {isDownloading} />
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- right side -->
|
||||
|
||||
<div
|
||||
transition:fly={{ y: 100, easing: sineInOut }}
|
||||
class="w-full mx-auto md:w-[48%] md:min-w-[400px] md:max-w-[500px] px-5 md:px-1 md:mt-0 mt-5 md:p-5"
|
||||
>
|
||||
<div class="hidden md:block">
|
||||
<Report />
|
||||
|
||||
<!-- download & share button -->
|
||||
<div class="mt-9 pt-8 px-2 h-auto border-t">
|
||||
<DownloadButton {isDisabled} {isDownloading} text="Download Image" />
|
||||
|
||||
<!-- share button -->
|
||||
<div class="w-full md:w-[70%] mx-auto my-5">
|
||||
<h1 class="text-sm font-semibold text-center">Share on image on social media:</h1>
|
||||
|
||||
<div
|
||||
class="flex justify-evenly border rounded-xl gap-5 w-[60%] md:w-[80%] px-5 py-2 mt-2 mx-auto"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
on:click={shareOnFacebook}
|
||||
class="w-10 hover:scale-[1.2] transition-all duration-300"
|
||||
>
|
||||
<LogoFacebook size={32} />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
on:click={shareOnLinkedIn}
|
||||
class="w-10 hover:scale-[1.2] transition-all duration-300"
|
||||
>
|
||||
<LogoLinkedin size={32} />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
on:click={shareOnTwitter}
|
||||
class="w-5 hover:scale-[1.2] transition-all duration-300"
|
||||
>
|
||||
<img src="/twitter_logo.png" alt="X.com" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if showReport}
|
||||
<!-- hide button -->
|
||||
<div class="flex justify-end mb-5">
|
||||
<button
|
||||
type="button"
|
||||
on:click={() => {
|
||||
showReport = false;
|
||||
showSetter = true;
|
||||
}}
|
||||
class="flex justify-between items-center gap-2 px-3 py-1 rounded-full text-white text-xs bg-[#0233BD]"
|
||||
>
|
||||
<img src="/free-tools/progress-report/hide-icon.svg" alt="preview icon" class="w-4" />
|
||||
Close
|
||||
</button>
|
||||
</div>
|
||||
<Report />
|
||||
|
||||
<!-- download & share button -->
|
||||
<div class="mt-9 pt-8 px-2 h-auto border-t">
|
||||
<DownloadButton {isDisabled} {isDownloading} />
|
||||
|
||||
<!-- share button -->
|
||||
<div class="w-full md:w-[70%] mx-auto my-5">
|
||||
<h1 class="text-sm font-semibold text-center">Share on image on social media:</h1>
|
||||
|
||||
<div
|
||||
class="flex justify-evenly border rounded-xl gap-5 w-[60%] md:w-[80%] px-5 py-2 mt-2 mx-auto"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
on:click={shareOnFacebook}
|
||||
class="w-10 hover:scale-[1.2] transition-all duration-300"
|
||||
>
|
||||
<LogoFacebook size={32} />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
on:click={shareOnLinkedIn}
|
||||
class="w-10 hover:scale-[1.2] transition-all duration-300"
|
||||
>
|
||||
<LogoLinkedin size={32} />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
on:click={shareOnTwitter}
|
||||
class="w-5 hover:scale-[1.2] transition-all duration-300"
|
||||
>
|
||||
<img src="/twitter_logo.png" alt="X.com" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<style>
|
||||
.range-input {
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
width: 80%;
|
||||
cursor: pointer;
|
||||
outline: none;
|
||||
border-radius: 15px;
|
||||
height: 1rem;
|
||||
}
|
||||
|
||||
.range-input::-webkit-slider-thumb {
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
height: 1.5rem;
|
||||
width: 1.5rem;
|
||||
background-color: #0f62fe;
|
||||
border-radius: 50%;
|
||||
border: none;
|
||||
transition: 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
.range-input::-moz-range-thumb {
|
||||
height: 1rem;
|
||||
width: 1rem;
|
||||
background-color: yellow;
|
||||
border-radius: 50%;
|
||||
transition: 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
.range-input:active::-webkit-slider-thumb {
|
||||
border: 3px solid #fff;
|
||||
filter: drop-shadow(0px 1px 1px #000000);
|
||||
}
|
||||
|
||||
.range-input:focus::-webkit-slider-thumb {
|
||||
border: 3px solid #fff;
|
||||
filter: drop-shadow(0px 1px 1px #000000);
|
||||
}
|
||||
|
||||
.range-input::-moz-range-thumb:hover {
|
||||
border: 3px solid #fff;
|
||||
box-shadow: 0 0 0 10px rgba(255, 85, 0, 0.1);
|
||||
}
|
||||
|
||||
.range-input:active::-moz-range-thumb {
|
||||
border: 3px solid #fff;
|
||||
filter: drop-shadow(0px 1px 1px #000000);
|
||||
}
|
||||
|
||||
.range-input:focus::-moz-range-thumb {
|
||||
border: 3px solid #fff;
|
||||
filter: drop-shadow(0px 1px 1px #000000);
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,88 @@
|
||||
<script lang="ts">
|
||||
import { openModal, htmlBody } from './store';
|
||||
import Modal from '$lib/Modal/Modal.svelte';
|
||||
|
||||
interface Avatar {
|
||||
src: string;
|
||||
}
|
||||
|
||||
let isAddIcon = false;
|
||||
let hoveredIndex: number | null = null;
|
||||
|
||||
let avatars: Avatar[] = [
|
||||
{ src: 'avatar_a' },
|
||||
{ src: 'avatar_b' },
|
||||
{ src: 'avatar_c' },
|
||||
{ src: 'avatar_d' },
|
||||
{ src: 'avatar_e' },
|
||||
{ src: 'avatar_f' },
|
||||
{ src: 'avatar_g' },
|
||||
{ src: 'avatar_h' },
|
||||
{ src: 'avatar_i' },
|
||||
{ src: 'avatar_j' },
|
||||
{ src: 'avatar_k' },
|
||||
{ src: 'avatar_l' },
|
||||
{ src: 'avatar_m' },
|
||||
{ src: 'avatar_n' },
|
||||
{ src: 'avatar_o' },
|
||||
{ src: 'avatar_p' },
|
||||
{ src: 'avatar_q' },
|
||||
{ src: 'avatar_r' },
|
||||
{ src: 'avatar_s' },
|
||||
{ src: 'avatar_t' }
|
||||
];
|
||||
|
||||
function selectAvatar(avatar: Avatar) {
|
||||
$htmlBody.avatar = avatar.src;
|
||||
$openModal.avatar = false;
|
||||
}
|
||||
</script>
|
||||
|
||||
<Modal bind:open={$openModal.avatar}>
|
||||
<!-- heading -->
|
||||
<div class="flex justify-between">
|
||||
<h1 class="text-sm font-semibold">Choose your Avatars</h1>
|
||||
<button
|
||||
type="button"
|
||||
on:click={() => ($openModal.avatar = false)}
|
||||
class="p-2 bg-[#F1F6FF] hover:scale-110 transition-all duration-300 rounded-full w-6"
|
||||
>
|
||||
<img src="/free-tools/progress-report/close-icon.svg" alt="Close icon" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- map for each avatar -->
|
||||
<div class="flex flex-wrap justify-evenly gap-y-3 mt-3">
|
||||
{#each avatars as avatar, index}
|
||||
<button
|
||||
on:click={() => {
|
||||
selectAvatar(avatar);
|
||||
}}
|
||||
on:mouseenter={() => {
|
||||
hoveredIndex = index;
|
||||
isAddIcon = true;
|
||||
}}
|
||||
on:mouseleave={() => {
|
||||
hoveredIndex = null;
|
||||
isAddIcon = false;
|
||||
}}
|
||||
class="relative w-[17%] flex justify-center items-center shadow-sm rounded-full hover:scale-110 transition-all duration-300"
|
||||
>
|
||||
<!-- + icon overlay -->
|
||||
{#if isAddIcon && hoveredIndex === index}
|
||||
<div
|
||||
class="w-full h-full flex justify-center items-center bg-white border absolute rounded-full opacity-[0.7] blur-md"
|
||||
></div>
|
||||
<img src="/free-tools/progress-report/hover-plus-icon.svg" alt="" class="absolute" />
|
||||
{/if}
|
||||
<!-- avatar -->
|
||||
<img
|
||||
src="https://assets.cdn.clsrio.com/progress-report/avatar/{avatar.src}.svg"
|
||||
alt=""
|
||||
class="w-full"
|
||||
/>
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
<!-- -->
|
||||
</Modal>
|
||||
@@ -0,0 +1,87 @@
|
||||
<script lang="ts">
|
||||
import Modal from '$lib/Modal/Modal.svelte';
|
||||
import { htmlBody, openModal } from './store';
|
||||
|
||||
interface Background {
|
||||
src: string;
|
||||
}
|
||||
|
||||
let backgrounds: Background[] = [
|
||||
{
|
||||
src: 'blue_tetiary_background'
|
||||
},
|
||||
{
|
||||
src: 'blue_secondary_background'
|
||||
},
|
||||
{
|
||||
src: 'blue_primary_background'
|
||||
},
|
||||
{
|
||||
src: 'purple_tetiary_background'
|
||||
},
|
||||
{
|
||||
src: 'purple_secondary_background'
|
||||
},
|
||||
{
|
||||
src: 'purple_primary_background'
|
||||
},
|
||||
{
|
||||
src: 'black_tetiary_background'
|
||||
},
|
||||
{
|
||||
src: 'black_secondary_background'
|
||||
},
|
||||
{
|
||||
src: 'black_primary_background'
|
||||
},
|
||||
{
|
||||
src: 'yellow_tetiary_background'
|
||||
},
|
||||
{
|
||||
src: 'yellow_secondary_background'
|
||||
},
|
||||
{
|
||||
src: 'yellow_primary_background'
|
||||
}
|
||||
];
|
||||
|
||||
function selectBackground(background: Background) {
|
||||
$htmlBody.background = background.src;
|
||||
$openModal.background = false;
|
||||
}
|
||||
</script>
|
||||
|
||||
<Modal className="h-full max-h-[60vh]" bind:open={$openModal.background}>
|
||||
<!-- -->
|
||||
<div class="flex justify-between">
|
||||
<h1 class="text-sm font-semibold">Choose your Background</h1>
|
||||
<button
|
||||
type="button"
|
||||
on:click={() => ($openModal.background = false)}
|
||||
class="p-2 bg-[#F1F6FF] hover:scale-110 transition-all duration-300 rounded-full w-6"
|
||||
>
|
||||
<img src="/free-tools/progress-report/close-icon.svg" alt="Close icon" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- -->
|
||||
<div
|
||||
class="flex flex-wrap justify-between gap-y-3 mt-3 overflow-y-auto overflow-x-hidden h-[85%]"
|
||||
>
|
||||
{#each backgrounds as background}
|
||||
<button
|
||||
on:click={() => {
|
||||
selectBackground(background);
|
||||
}}
|
||||
class="flex w-[30%] items-center shadow-sm hover:scale-110 transition-all duration-300"
|
||||
>
|
||||
<img
|
||||
src="https://assets.cdn.clsrio.com/progress-report/backgrounds/{background.src}.webp"
|
||||
alt=""
|
||||
class=""
|
||||
/>
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
<!-- -->
|
||||
</Modal>
|
||||
@@ -0,0 +1,52 @@
|
||||
<script lang="ts">
|
||||
import { toPng } from 'html-to-image';
|
||||
import { nodeStore, htmlBody, openModal } from './store';
|
||||
|
||||
export let isDisabled: boolean;
|
||||
export let isDownloading: boolean;
|
||||
export let text: string = 'Generate Progress Report';
|
||||
|
||||
function convertToPng() {
|
||||
isDownloading = true;
|
||||
$openModal.showFullscreenButton = false;
|
||||
|
||||
const fileName = `${$htmlBody.name} - ${$htmlBody.mood.text} (ClassroomIO_com)`;
|
||||
|
||||
if ($nodeStore) {
|
||||
setTimeout(() => {
|
||||
toPng($nodeStore, {
|
||||
width: 500,
|
||||
height: 500,
|
||||
quality: 1
|
||||
})
|
||||
.then((dataUrl) => {
|
||||
const link = document.createElement('a');
|
||||
link.download = fileName;
|
||||
link.href = dataUrl;
|
||||
link.click();
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Oops, something went wrong!', error);
|
||||
})
|
||||
.finally(() => {
|
||||
isDownloading = false;
|
||||
$openModal.showFullscreenButton = true;
|
||||
});
|
||||
}, 300);
|
||||
} else {
|
||||
console.error('Node is not defined');
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
disabled={isDownloading || !isDisabled ? true : false}
|
||||
on:click={convertToPng}
|
||||
class="{isDownloading || !isDisabled
|
||||
? 'bg-gray-200 cursor-not-allowed text-gray-600'
|
||||
: 'bg-[#0233BD] text-white'} {text === 'Generate Progress Report'
|
||||
? 'block md:hidden'
|
||||
: ''} mt-5 transition-all delay-100 text-white text-xs font-semibold w-full py-3 rounded-md"
|
||||
>{isDownloading ? 'Loading...' : text}</button
|
||||
>
|
||||
@@ -0,0 +1,11 @@
|
||||
<script lang="ts">
|
||||
import Modal from '$lib/Modal/Modal.svelte';
|
||||
import Report from './Report.svelte';
|
||||
import { openModal } from './store';
|
||||
</script>
|
||||
|
||||
<Modal bind:open={$openModal.fullscreen}>
|
||||
<!-- -->
|
||||
<Report />
|
||||
<!-- -->
|
||||
</Modal>
|
||||
@@ -0,0 +1,100 @@
|
||||
<script lang="ts">
|
||||
import Modal from '$lib/Modal/Modal.svelte';
|
||||
import { htmlBody, openModal } from './store';
|
||||
|
||||
interface Mood {
|
||||
text: string;
|
||||
iconSrc: string;
|
||||
}
|
||||
|
||||
let moods: Mood[] = [
|
||||
{
|
||||
text: 'Unmotivated',
|
||||
iconSrc: 'unmotivated'
|
||||
},
|
||||
{
|
||||
text: 'Feeling Enlightened',
|
||||
iconSrc: 'feeling-enlightened'
|
||||
},
|
||||
{
|
||||
text: 'Enthusiastic',
|
||||
iconSrc: 'enthusiastic'
|
||||
},
|
||||
{
|
||||
text: 'Confused',
|
||||
iconSrc: 'confused'
|
||||
},
|
||||
{
|
||||
text: 'Feeling stuck',
|
||||
iconSrc: 'feeling-stuck'
|
||||
},
|
||||
// {
|
||||
// text: 'Believing in the process',
|
||||
// iconSrc: 'believing-in-the-process'
|
||||
// },
|
||||
{
|
||||
text: 'Curious',
|
||||
iconSrc: 'curious'
|
||||
},
|
||||
{
|
||||
text: 'Achieving Mastery',
|
||||
iconSrc: 'achieving-mastery'
|
||||
},
|
||||
{
|
||||
text: 'Feeling Positive',
|
||||
iconSrc: 'feeling-positive'
|
||||
},
|
||||
{
|
||||
text: 'Reflecting',
|
||||
iconSrc: 'reflecting'
|
||||
},
|
||||
{
|
||||
text: 'Making progress',
|
||||
iconSrc: 'progress'
|
||||
},
|
||||
{
|
||||
text: 'Feeling Sad',
|
||||
iconSrc: 'sad'
|
||||
}
|
||||
];
|
||||
|
||||
function selectMood(mood: Mood) {
|
||||
$htmlBody.mood.text = mood.text;
|
||||
$htmlBody.mood.iconSrc = mood.iconSrc;
|
||||
$openModal.mood = false;
|
||||
}
|
||||
</script>
|
||||
|
||||
<Modal bind:open={$openModal.mood}>
|
||||
<!-- header -->
|
||||
<div class="flex justify-between">
|
||||
<h1 class="text-sm font-semibold">Choose your Mood</h1>
|
||||
<button
|
||||
type="button"
|
||||
on:click={() => ($openModal.mood = false)}
|
||||
class="p-2 bg-[#F1F6FF] hover:scale-110 transition-all duration-300 rounded-full w-6"
|
||||
>
|
||||
<img src="/free-tools/progress-report/close-icon.svg" alt="Close icon" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- mapping -->
|
||||
<div class="flex flex-wrap justify-between gap-y-3 mt-3">
|
||||
{#each moods as mood}
|
||||
<button
|
||||
on:click={() => {
|
||||
selectMood(mood);
|
||||
}}
|
||||
class="flex gap-3 items-center shadow-sm rounded-full px-4 py-1 bg-[#F1F6FF] hover:bg-[#0233BD] hover:text-white transition-all duration-700"
|
||||
>
|
||||
<p class="text-xs md:text-sm font-semibold">{mood.text}</p>
|
||||
<img
|
||||
src="https://assets.cdn.clsrio.com/progress-report/emojis/{mood.iconSrc}.png"
|
||||
alt={mood.text}
|
||||
class="w-4"
|
||||
/>
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
<!-- -->
|
||||
</Modal>
|
||||
@@ -0,0 +1,142 @@
|
||||
<script lang="ts">
|
||||
import { htmlBody, nodeStore, openModal } from './store';
|
||||
|
||||
import { derived } from 'svelte/store';
|
||||
|
||||
const colorMap = {
|
||||
blue_tetiary_background: '#0542CC',
|
||||
blue_secondary_background: '#0542CC',
|
||||
blue_primary_background: '#0542CC',
|
||||
purple_tetiary_background: '#9D4EDD',
|
||||
purple_secondary_background: '#9D4EDD',
|
||||
purple_primary_background: '#9D4EDD',
|
||||
black_tetiary_background: '#252525',
|
||||
black_secondary_background: '#252525',
|
||||
black_primary_background: '#252525',
|
||||
yellow_tetiary_background: '#FFBC42',
|
||||
yellow_secondary_background: '#FFBC42',
|
||||
yellow_primary_background: '#FFBC42'
|
||||
};
|
||||
|
||||
type ColorMapKey = keyof typeof colorMap;
|
||||
|
||||
const backgroundColor = derived(htmlBody, ($htmlBody) => {
|
||||
return colorMap[$htmlBody.background as ColorMapKey] || '#1e5ed2';
|
||||
});
|
||||
|
||||
let node: any;
|
||||
|
||||
$: nodeStore.set(node);
|
||||
|
||||
$: rootBgImage = `https://assets.cdn.clsrio.com/progress-report/backgrounds/${
|
||||
$htmlBody.background || 'blue_tetiary_background'
|
||||
}.webp`;
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="h-full min-h-[500px] max-h-[500px] md:h-[60vh] w-full border relative flex items-center justify-center"
|
||||
style={`font-family: "Space Grotesk", sans-serif; background-image: url('${rootBgImage}'); background-size: cover; background-repeat: no-repeat`}
|
||||
bind:this={node}
|
||||
>
|
||||
<!-- fullscreen button -->
|
||||
{#if $openModal.showFullscreenButton}
|
||||
{#if $openModal.fullscreen}
|
||||
<button
|
||||
type="button"
|
||||
on:click={() => ($openModal.fullscreen = !$openModal.fullscreen)}
|
||||
class="absolute top-4 right-4 p-1.5 rounded-full hover:scale-90 transition-all duration-300 bg-white"
|
||||
>
|
||||
<img src="/free-tools/progress-report/close-icon.svg" alt="" class="" />
|
||||
</button>
|
||||
{:else}
|
||||
<button
|
||||
type="button"
|
||||
on:click={() => ($openModal.fullscreen = !$openModal.fullscreen)}
|
||||
class="absolute top-4 right-4 w-7 hover:scale-90 transition-all duration-300"
|
||||
>
|
||||
<img src="/free-tools/progress-report/full-screen-icon.svg" alt="" class="" />
|
||||
</button>
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
<div class="mt-20">
|
||||
<!-- using both md & lg for the width here because the lib doesn't consider md to be medium screens and above, using md alone will make the output break on desktop so i had to also specify for large screens -->
|
||||
<div
|
||||
class="rounded-lg shadow-md bg-white border-4 border-gray-100 z-20 mx-auto flex justify-center items-center w-[16rem] h-[16rem]"
|
||||
>
|
||||
<div class="relative w-full h-full flex flex-col justify-center gap-3 items-center px-2">
|
||||
<!-- avatar -->
|
||||
<img
|
||||
src={`https://assets.cdn.clsrio.com/progress-report/avatar/${
|
||||
$htmlBody.avatar || 'avatar_a'
|
||||
}.svg`}
|
||||
alt=""
|
||||
class="absolute top-0 w-[25%] h-[25%] ml-2 -mt-[2.2rem]"
|
||||
/>
|
||||
|
||||
<!-- mood -->
|
||||
{#if $htmlBody.name || $htmlBody.mood.text}
|
||||
<div
|
||||
style="background-color: {$backgroundColor}; color: white; display: flex;"
|
||||
class="px-3 py-0.5 border border-[#EDEDED] rounded-2xl mt-6 md:mt-3 font-semibold text-[10px] md:text-[0.7rem] flex items-center justify-between"
|
||||
>
|
||||
<span class="mr-0.5">{$htmlBody.name}</span>
|
||||
|
||||
{#if $htmlBody.mood.text}
|
||||
<span class="flex gap-1 items-center">
|
||||
<p class="font-semibold text-[10px] md:text-[0.7rem]">
|
||||
is {$htmlBody.mood.text}
|
||||
</p>
|
||||
<img
|
||||
src="https://assets.cdn.clsrio.com/progress-report/emojis/{$htmlBody.mood
|
||||
.iconSrc}.png"
|
||||
alt={$htmlBody.mood.text}
|
||||
class="w-3"
|
||||
/>
|
||||
</span>
|
||||
{/if}
|
||||
</div>
|
||||
{:else}
|
||||
<p
|
||||
style="background-color: {$backgroundColor}"
|
||||
class="px-3 py-0.5 border border-[#EDEDED] text-white rounded-2xl mt-3 font-medium text-[9px]"
|
||||
>
|
||||
Add your name
|
||||
</p>
|
||||
{/if}
|
||||
|
||||
<!-- learning -->
|
||||
<span
|
||||
class="w-full md:my-2 p-1 overflow-hidden text-[0.7rem] md:text-[0.8rem] font-normal text-center outline-none h-[10vh] md:h-[12vh]"
|
||||
>
|
||||
{$htmlBody.learning}
|
||||
</span>
|
||||
|
||||
<!-- progress -->
|
||||
<div class="flex flex-col justify-center items-center mb-2 md:mb-0">
|
||||
<div class="border border-[#d9e0f5] w-full h-4 rounded-full p-0.5 bg-[#f1f6ff]">
|
||||
<div
|
||||
class="progress-range rounded-full w-full h-full"
|
||||
style="background: {$backgroundColor}; width: {$htmlBody.progress}%"
|
||||
/>
|
||||
</div>
|
||||
<p class="text-[12px] font-semibold mt-1">Progress Achieved: {$htmlBody.progress}%</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
style="display: flex;"
|
||||
class="border mt-20 bottom-5 w-[50%] mx-auto flex gap-1 items-center justify-center bg-white rounded-md px-2 py-1"
|
||||
>
|
||||
<img src="/logo-192.png" alt="classroomio logo" class="w-4" />
|
||||
<p class="text-[10px] font-bold">ClassroomIO.com</p>
|
||||
</div>
|
||||
<!-- -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- importing font family -->
|
||||
<style>
|
||||
@import url('https://fonts.googleapis.com/css2?family=Space+Grotesk:[email protected]&display=swap');
|
||||
</style>
|
||||
@@ -0,0 +1,59 @@
|
||||
import { derived, writable } from 'svelte/store';
|
||||
import type { Writable } from 'svelte/store';
|
||||
|
||||
interface OpenModal {
|
||||
avatar: boolean;
|
||||
background: boolean;
|
||||
fullscreen: boolean;
|
||||
showFullscreenButton: boolean;
|
||||
mood: boolean;
|
||||
}
|
||||
|
||||
export interface HtmlBody {
|
||||
name: string;
|
||||
learning: string;
|
||||
progress: number;
|
||||
avatar: string;
|
||||
background: string;
|
||||
mood: {
|
||||
text: string;
|
||||
iconSrc: string;
|
||||
};
|
||||
}
|
||||
|
||||
// store for the node binded to the report component for the component to image conversion
|
||||
export const nodeStore = writable(null);
|
||||
|
||||
export const openModal: Writable<OpenModal> = writable({
|
||||
avatar: false,
|
||||
background: false,
|
||||
fullscreen: false,
|
||||
showFullscreenButton: true,
|
||||
mood: false
|
||||
});
|
||||
|
||||
const placeholderLearning = 'Tell us what you are learning';
|
||||
|
||||
// report html
|
||||
export const htmlBody: Writable<HtmlBody> = writable({
|
||||
name: '',
|
||||
learning: placeholderLearning,
|
||||
progress: 10,
|
||||
avatar: '',
|
||||
background: '',
|
||||
mood: {
|
||||
text: '',
|
||||
iconSrc: ''
|
||||
}
|
||||
});
|
||||
|
||||
// a mini validation for the htmlBody store that ensures all fields are filled
|
||||
export const isFormComplete = derived(htmlBody, ($htmlBody) => {
|
||||
return !!(
|
||||
$htmlBody.name.trim() &&
|
||||
$htmlBody.learning.trim() !== placeholderLearning &&
|
||||
$htmlBody.progress > 0 &&
|
||||
$htmlBody.mood.text.trim() &&
|
||||
$htmlBody.mood.iconSrc.trim()
|
||||
);
|
||||
});
|
||||
Reference in New Issue
Block a user