Skip to content

Commit

Permalink
Added save buttons
Browse files Browse the repository at this point in the history
  • Loading branch information
marlonbaeten committed May 28, 2024
1 parent 5b95e5c commit fac95e9
Show file tree
Hide file tree
Showing 6 changed files with 278 additions and 64 deletions.
26 changes: 24 additions & 2 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import Results from './Results';
import Table from './Table';
import usePersistence from './usePersistence';
import usePersistence, { upload } from './usePersistence';
import { copyLink, downloadFile, saveStateLocal } from './util';

export default function App(): JSX.Element {
const { state, select, mark, note } = usePersistence();
const { state, select, mark, note, setState } = usePersistence();

return (
<>
Expand All @@ -19,11 +20,32 @@ export default function App(): JSX.Element {
<li key={level}>{level}</li>
))}
</ol>
<p>
<strong>Markeren</strong> kan door het vakje rechts te selecteren.
Onder aan de pagina verschijnt een lijst van gemarkeerde items.
</p>
</div>
<hr />
<Table state={state} select={select} mark={mark} />
<hr />
<Results state={state} mark={mark} note={note} />
<hr />
<h2>Opslaan</h2>
<p className="save">
<button onClick={() => saveStateLocal(state)}>
Opslaan in de browser
</button>
<button onClick={() => copyLink(state)}>Unieke link kopiëren</button>
<button onClick={() => downloadFile(state)}>Bestand downloaden</button>
<label htmlFor="upload">
<span>Bestand laden</span>
<input
type="file"
id="upload"
onChange={(e) => upload(e.target, setState)}
/>
</label>
</p>
</>
);
}
41 changes: 25 additions & 16 deletions src/ExperienceRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,19 +22,14 @@ export default function ExperienceRow({
onSelect,
}: ExperienceRowProps): JSX.Element {
return (
<tr className="selectable">
<td style={{ borderBottomColor: color, borderLeftColor: color }}>
<label htmlFor={`check-${themeIndex}-${index}`} title="markeren">
<input
type="checkbox"
id={`check-${themeIndex}-${index}`}
name={`check-${themeIndex}-${index}`}
checked={marked}
onChange={(e) => onMark(e.target.checked)}
/>
</label>
</td>
<th scope="row" style={{ borderBottomColor: color }}>
<tr className={`selectable ${marked ? 'marked' : ''}`}>
<th
scope="row"
style={{
borderBottomColor: color,
borderLeftColor: color,
}}
>
{name}
</th>
{levels
Expand All @@ -47,11 +42,9 @@ export default function ExperienceRow({
key={level}
style={{
borderBottomColor: color,
borderRightColor:
levelIndex === levels.length - 1 ? color : undefined,
}}
>
<label htmlFor={key} title={level}>
<label htmlFor={key} data-tooltip={level}>
<input
type="radio"
name={`option-${themeIndex}-${index}`}
Expand All @@ -63,6 +56,22 @@ export default function ExperienceRow({
</label>
</td>
))}
<td
style={{
borderBottomColor: color,
borderRightColor: color,
}}
>
<label htmlFor={`check-${themeIndex}-${index}`} data-tooltip="markeren">
<input
type="checkbox"
id={`check-${themeIndex}-${index}`}
name={`check-${themeIndex}-${index}`}
checked={marked}
onChange={(e) => onMark(e.target.checked)}
/>
</label>
</td>
</tr>
);
}
11 changes: 9 additions & 2 deletions src/Table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,17 @@ export default function Table({
<table className="form">
<thead>
<tr>
<th colSpan={2} style={{ border: 'none' }}></th>
<th style={{ border: 'none' }}></th>
{state.levels.map((level, levelIndex) => (
<th key={level}>{levelIndex + 1}</th>
<th key={level} data-tooltip={level}>
{levelIndex + 1}
</th>
))}
<th data-tooltip="markeren">
<svg viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg">
<path d="M451.47 125.404l-70.904-70.902a13.11 13.11 0 00-17.746-.733L164.999 221.372l119.603 119.602 167.6-197.822a13.11 13.11 0 00-.733-17.748zM116.153 267.658l29.002-29.001 122.16 122.16-29.001 29.002zM94.552 363.73l47.69 47.69c18.523-11.008 40.65-15.58 63.161-17.391l-93.46-93.46c-1.811 22.511-6.383 44.638-17.39 63.162zm-35.938 39.898a6.574 6.574 0 00-1.273 7.501l22.375 46.487a6.575 6.575 0 0010.572 1.796l31.411-31.41-43.728-43.73z" />
</svg>
</th>
</tr>
</thead>
<tbody>
Expand Down
84 changes: 83 additions & 1 deletion src/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -115,12 +115,17 @@ textarea {
margin-bottom: 0.5rem;
border: 1px solid #eaeaea;
background-color: #fafafa;
max-width: 30rem;
}

.legenda h2 {
margin-bottom: 0.5rem;
}

.legenda ol {
margin-bottom: 0.5rem;
}

/* table styling */

table {
Expand Down Expand Up @@ -150,8 +155,16 @@ table.form th.theme {
border-bottom: 1px solid #000;
}

table.form tr.marked {
background-color: rgba(255, 225, 0, 0.1);
}

table.form thead th {
padding: 0.5rem;
}

table.form tbody td {
width: 50px;
width: 2.5rem;
}

table.form tbody td label {
Expand Down Expand Up @@ -191,6 +204,14 @@ table.form tr.selectable:hover {
background: #fafafa;
}

@media screen and (max-width: 768px) {
table.form th:last-child {
max-width: 2rem;
text-overflow: ellipsis;
overflow: hidden;
}
}

/* result table styling */

.result table {
Expand Down Expand Up @@ -247,3 +268,64 @@ table.form tr.selectable:hover {
padding: 0.25rem 0.5rem;
}
}

.save span,
.save button {
display: inline-block;
background: #4472c4;
color: #fff;
padding: 0.5rem 1rem;
border: none;
cursor: pointer;
line-height: 1.5rem;
font-size: 1rem;
margin: 0.5rem 0.5rem 0 0;
}

.save span:hover,
.save button:hover {
background: #2e6ab3;
}

.save input {
display: none;
}

[data-tooltip] {
position: relative;
}

[data-tooltip]::before {
content: "";
position: absolute;
top: -1px;
left: 50%;
transform: translateX(-50%);
border-width: 0.25rem 0.5rem 0 0.5rem;
border-style: solid;
border-color: rgba(0, 0, 0, 0.7) transparent transparent transparent;
z-index: 100;
opacity: 0;
}

[data-tooltip]::after {
content: attr(data-tooltip);
position: absolute;
left: 50%;
top: 0;
transform: translateX(-50%) translateY(-100%);
background: rgba(0, 0, 0, 0.7);
text-align: center;
color: #fff;
padding: 0.25rem 0.5rem;
font-size: 0.8rem;
white-space: nowrap;
border-radius: 0.25rem;
pointer-events: none;
opacity: 0;
}

[data-tooltip]:hover::after,
[data-tooltip]:hover::before {
opacity: 1
}
81 changes: 38 additions & 43 deletions src/usePersistence.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
import { Reducer, useEffect, useReducer } from 'react';
import { Reducer, useReducer } from 'react';
import rawData from './data.json';
import { decode } from './util';

export interface StorageState {
levels: number[][];
marked: boolean[][];
notes: string;
}

export interface State {
levels: string[];
Expand All @@ -22,13 +29,15 @@ export interface Experience {
}

export type Select = (theme: number, experience: number, level: number) => void;

export type Mark = (theme: number, experience: number, marked: boolean) => void;

export interface PersistenceState {
state: State;
select: Select;
mark: Mark;
note: (content: string) => void;
setState: (state: State) => void;
}

export type ExperienceAction =
Expand All @@ -50,6 +59,10 @@ export type Action =
| {
type: 'note';
content: string;
}
| {
type: 'state';
state: State;
};

const data = rawData as State;
Expand Down Expand Up @@ -91,40 +104,20 @@ function reducer(state: State, action: Action): State {
return updateState(state, action, { marked: action.marked });
case 'note':
return { ...state, notes: action.content };
case 'state':
return action.state;
default:
throw new Error();
}
}

function initialState(): State {
const localState = window.localStorage.getItem('persistence');
const defaultState = { levels: [], marked: [], notes: '' };
let { levels, marked, notes } = localState
? JSON.parse(localState)
: defaultState;

if (!levels || !marked) {
levels = [];
marked = [];
notes = '';
}
const localState =
window.location.hash.slice(1) ||
window.localStorage.getItem('persistence') ||
'';

return {
...data,
notes,
themes: data.themes.map((theme, themeIndex) => ({
...theme,
experiences: theme.experiences.map((experience, experienceIndex) => ({
...experience,
level: levels[themeIndex]
? levels[themeIndex][experienceIndex] || 0
: 0,
marked: marked[themeIndex]
? marked[themeIndex][experienceIndex] || false
: false,
})),
})),
};
return decode(data, localState);
}

export default function usePersistence(): PersistenceState {
Expand All @@ -133,27 +126,29 @@ export default function usePersistence(): PersistenceState {
initialState()
);

useEffect(() => {
window.localStorage.setItem(
'persistence',
JSON.stringify({
levels: state.themes.map((theme) =>
theme.experiences.map((experience) => experience.level)
),
marked: state.themes.map((theme) =>
theme.experiences.map((experience) => experience.marked)
),
notes: state.notes,
})
);
}, [state]);

return {
state,
select: (theme: number, experience: number, level: number) =>
dispatch({ type: 'select', theme, experience, level }),
mark: (theme: number, experience: number, marked: boolean) =>
dispatch({ type: 'mark', theme, experience, marked }),
note: (content: string) => dispatch({ type: 'note', content }),
setState: (state: State) => dispatch({ type: 'state', state: state }),
};
}

export function upload(
input: HTMLInputElement,
setState: (state: State) => void
) {
if (!input.files || !input.files[0]) {
return;
}

const reader = new FileReader();
reader.addEventListener('load', () => {
setState(decode(data, reader.result?.toString() || ''));
window.alert('Het bestand is succesvol geladen.');
});
reader.readAsText(input.files[0]);
}
Loading

0 comments on commit fac95e9

Please sign in to comment.