Verbeter grafische kwaliteit van kleurplaten (v0.4.67-beta) #5
4 changed files with 45 additions and 17 deletions
2
VERSION
2
VERSION
|
|
@ -1 +1 @@
|
||||||
0.3.13-beta
|
0.3.14-beta
|
||||||
|
|
|
||||||
7
db/008_widget_share.sql
Normal file
7
db/008_widget_share.sql
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
-- Widget-delen: een toewijzing kan naast een heel bord (widget_id NULL,
|
||||||
|
-- bestaand gedrag) ook één enkele widget van dat bord delen, met een modus:
|
||||||
|
-- 'kijken' (leerling ziet de widget read-only, snellere poll) of 'werken'
|
||||||
|
-- (leerling werkt erin; voortgang loopt via progress_events). widget_id
|
||||||
|
-- verwijst naar het stabiele wid in de borddata van de leerkracht.
|
||||||
|
ALTER TABLE assignments ADD COLUMN IF NOT EXISTS widget_id TEXT;
|
||||||
|
ALTER TABLE assignments ADD COLUMN IF NOT EXISTS mode TEXT NOT NULL DEFAULT 'werken';
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
/* version — shown until /api/version resolves (or if the fetch fails, e.g. offline).
|
/* version — shown until /api/version resolves (or if the fetch fails, e.g. offline).
|
||||||
Kept in sync by hand with the VERSION file at the repo root on every release. */
|
Kept in sync by hand with the VERSION file at the repo root on every release. */
|
||||||
const VERSION = "0.3.13-beta";
|
const VERSION = "0.3.14-beta";
|
||||||
(function(){
|
(function(){
|
||||||
const tag = document.getElementById("verTag");
|
const tag = document.getElementById("verTag");
|
||||||
tag.textContent = "v"+VERSION;
|
tag.textContent = "v"+VERSION;
|
||||||
|
|
|
||||||
51
src/api.js
51
src/api.js
|
|
@ -462,14 +462,34 @@ export default async function api(app) {
|
||||||
const describeAssignment = async (a) => {
|
const describeAssignment = async (a) => {
|
||||||
const t = (await pool.query('SELECT display_name, username, data FROM users WHERE id = $1', [a.teacher_id])).rows[0];
|
const t = (await pool.query('SELECT display_name, username, data FROM users WHERE id = $1', [a.teacher_id])).rows[0];
|
||||||
const board = t ? findBoardById(t.data, a.board_id) : null;
|
const board = t ? findBoardById(t.data, a.board_id) : null;
|
||||||
|
const widget = a.widget_id ? (board?.data?.widgets || []).find((w) => w.wid === a.widget_id) : null;
|
||||||
return {
|
return {
|
||||||
teacherId: Number(a.teacher_id),
|
teacherId: Number(a.teacher_id),
|
||||||
teacherName: t ? (t.display_name || t.username) : null,
|
teacherName: t ? (t.display_name || t.username) : null,
|
||||||
boardId: a.board_id,
|
boardId: a.board_id,
|
||||||
boardName: board ? board.name : null,
|
boardName: board ? board.name : null,
|
||||||
|
widgetId: a.widget_id || null,
|
||||||
|
widgetType: widget ? widget.id : null,
|
||||||
|
mode: a.mode || 'werken',
|
||||||
updatedAt: a.updated_at,
|
updatedAt: a.updated_at,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
// Gedeelde validatie voor beide PUT-routes: heel bord (widgetId null, bestaand
|
||||||
|
// gedrag) of één widget van dat bord, met modus kijken/werken. De widget moet
|
||||||
|
// echt in het opgegeven bord van de leerkracht zelf staan.
|
||||||
|
const parseShareBody = (req, reply) => {
|
||||||
|
const { boardId, widgetId, mode } = req.body ?? {};
|
||||||
|
if (!boardId || typeof boardId !== 'string' || boardId.length > 100) return fail(reply, 400, 'ongeldige boardId');
|
||||||
|
const board = findBoardById(req.user.data, boardId);
|
||||||
|
if (!board) return fail(reply, 400, 'bord onbekend of niet van deze gebruiker');
|
||||||
|
const m = mode ?? 'werken';
|
||||||
|
if (m !== 'kijken' && m !== 'werken') return fail(reply, 400, 'ongeldige modus');
|
||||||
|
if (widgetId != null) {
|
||||||
|
if (typeof widgetId !== 'string' || widgetId.length > 100) return fail(reply, 400, 'ongeldige widgetId');
|
||||||
|
if (!(board.data?.widgets || []).some((w) => w.wid === widgetId)) return fail(reply, 400, 'widget onbekend op dit bord');
|
||||||
|
}
|
||||||
|
return { boardId, widgetId: widgetId ?? null, mode: m };
|
||||||
|
};
|
||||||
|
|
||||||
app.get('/assignments/class/:id', async (req, reply) => {
|
app.get('/assignments/class/:id', async (req, reply) => {
|
||||||
need(req, reply, PERMISSIONS['assignments.manage']);
|
need(req, reply, PERMISSIONS['assignments.manage']);
|
||||||
|
|
@ -482,18 +502,17 @@ export default async function api(app) {
|
||||||
});
|
});
|
||||||
app.put('/assignments/class/:id', async (req, reply) => {
|
app.put('/assignments/class/:id', async (req, reply) => {
|
||||||
need(req, reply, PERMISSIONS['assignments.manage']);
|
need(req, reply, PERMISSIONS['assignments.manage']);
|
||||||
const { boardId } = req.body ?? {};
|
const share = parseShareBody(req, reply);
|
||||||
if (!boardId || typeof boardId !== 'string' || boardId.length > 100) return fail(reply, 400, 'ongeldige boardId');
|
if (share.error) return share;
|
||||||
if (!findBoardById(req.user.data, boardId)) return fail(reply, 400, 'bord onbekend of niet van deze gebruiker');
|
|
||||||
const c = (await pool.query('SELECT * FROM classes WHERE id = $1', [req.params.id])).rows[0];
|
const c = (await pool.query('SELECT * FROM classes WHERE id = $1', [req.params.id])).rows[0];
|
||||||
if (!c) return fail(reply, 404, 'klas onbekend');
|
if (!c) return fail(reply, 404, 'klas onbekend');
|
||||||
if (!sameSchool(req, c)) return fail(reply, 403, 'geen rechten');
|
if (!sameSchool(req, c)) return fail(reply, 403, 'geen rechten');
|
||||||
if (teacherOnly(req) && !(await classOwnedByTeacher(c.id, req.user.id))) return fail(reply, 403, 'geen rechten');
|
if (teacherOnly(req) && !(await classOwnedByTeacher(c.id, req.user.id))) return fail(reply, 403, 'geen rechten');
|
||||||
await pool.query(
|
await pool.query(
|
||||||
`INSERT INTO assignments (class_id, teacher_id, board_id) VALUES ($1,$2,$3)
|
`INSERT INTO assignments (class_id, teacher_id, board_id, widget_id, mode) VALUES ($1,$2,$3,$4,$5)
|
||||||
ON CONFLICT (class_id) WHERE class_id IS NOT NULL
|
ON CONFLICT (class_id) WHERE class_id IS NOT NULL
|
||||||
DO UPDATE SET teacher_id = $2, board_id = $3, updated_at = now()`,
|
DO UPDATE SET teacher_id = $2, board_id = $3, widget_id = $4, mode = $5, updated_at = now()`,
|
||||||
[c.id, req.user.id, boardId]);
|
[c.id, req.user.id, share.boardId, share.widgetId, share.mode]);
|
||||||
return { ok: true };
|
return { ok: true };
|
||||||
});
|
});
|
||||||
app.delete('/assignments/class/:id', async (req, reply) => {
|
app.delete('/assignments/class/:id', async (req, reply) => {
|
||||||
|
|
@ -521,17 +540,16 @@ export default async function api(app) {
|
||||||
});
|
});
|
||||||
app.put('/assignments/pupil/:id', async (req, reply) => {
|
app.put('/assignments/pupil/:id', async (req, reply) => {
|
||||||
need(req, reply, PERMISSIONS['assignments.manage']);
|
need(req, reply, PERMISSIONS['assignments.manage']);
|
||||||
const { boardId } = req.body ?? {};
|
const share = parseShareBody(req, reply);
|
||||||
if (!boardId || typeof boardId !== 'string' || boardId.length > 100) return fail(reply, 400, 'ongeldige boardId');
|
if (share.error) return share;
|
||||||
if (!findBoardById(req.user.data, boardId)) return fail(reply, 400, 'bord onbekend of niet van deze gebruiker');
|
|
||||||
const u = (await pool.query('SELECT * FROM users WHERE id = $1', [req.params.id])).rows[0];
|
const u = (await pool.query('SELECT * FROM users WHERE id = $1', [req.params.id])).rows[0];
|
||||||
if (!u) return fail(reply, 404, 'gebruiker onbekend');
|
if (!u) return fail(reply, 404, 'gebruiker onbekend');
|
||||||
if (!(await pupilAccessible(req, u))) return fail(reply, 403, 'geen rechten');
|
if (!(await pupilAccessible(req, u))) return fail(reply, 403, 'geen rechten');
|
||||||
await pool.query(
|
await pool.query(
|
||||||
`INSERT INTO assignments (pupil_id, teacher_id, board_id) VALUES ($1,$2,$3)
|
`INSERT INTO assignments (pupil_id, teacher_id, board_id, widget_id, mode) VALUES ($1,$2,$3,$4,$5)
|
||||||
ON CONFLICT (pupil_id) WHERE pupil_id IS NOT NULL
|
ON CONFLICT (pupil_id) WHERE pupil_id IS NOT NULL
|
||||||
DO UPDATE SET teacher_id = $2, board_id = $3, updated_at = now()`,
|
DO UPDATE SET teacher_id = $2, board_id = $3, widget_id = $4, mode = $5, updated_at = now()`,
|
||||||
[u.id, req.user.id, boardId]);
|
[u.id, req.user.id, share.boardId, share.widgetId, share.mode]);
|
||||||
return { ok: true };
|
return { ok: true };
|
||||||
});
|
});
|
||||||
app.delete('/assignments/pupil/:id', async (req, reply) => {
|
app.delete('/assignments/pupil/:id', async (req, reply) => {
|
||||||
|
|
@ -553,11 +571,14 @@ export default async function api(app) {
|
||||||
if (!a && req.user.class_id) {
|
if (!a && req.user.class_id) {
|
||||||
a = (await pool.query('SELECT * FROM assignments WHERE class_id = $1', [req.user.class_id])).rows[0];
|
a = (await pool.query('SELECT * FROM assignments WHERE class_id = $1', [req.user.class_id])).rows[0];
|
||||||
}
|
}
|
||||||
if (!a) return { widgets: [], updatedAt: null };
|
if (!a) return { widgets: [], mode: 'werken', updatedAt: null };
|
||||||
const t = (await pool.query('SELECT data FROM users WHERE id = $1', [a.teacher_id])).rows[0];
|
const t = (await pool.query('SELECT data FROM users WHERE id = $1', [a.teacher_id])).rows[0];
|
||||||
const board = t ? findBoardById(t.data, a.board_id) : null;
|
const board = t ? findBoardById(t.data, a.board_id) : null;
|
||||||
const widgets = (board?.data?.widgets || []).map((w) => ({ id: w.id, wid: w.wid, state: w.state }));
|
let widgets = (board?.data?.widgets || []).map((w) => ({ id: w.id, wid: w.wid, state: w.state }));
|
||||||
return { widgets, updatedAt: a.updated_at };
|
// één-widget-toewijzing: alleen die widget; is hij inmiddels van het bord
|
||||||
|
// verwijderd, dan een lege lijst (zelfde weergave als geen toewijzing)
|
||||||
|
if (a.widget_id) widgets = widgets.filter((w) => w.wid === a.widget_id);
|
||||||
|
return { widgets, mode: a.mode || 'werken', updatedAt: a.updated_at };
|
||||||
});
|
});
|
||||||
|
|
||||||
// ---- voortgang (leerling-omgeving) ---------------------------------------------
|
// ---- voortgang (leerling-omgeving) ---------------------------------------------
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue