Provide basic functionalities for create/update quiz

This commit is contained in:
andrea 2023-07-13 13:20:39 +02:00
parent 52c0c6f7f8
commit 091429be58
24 changed files with 356 additions and 145 deletions

2
.gitignore vendored
View file

@ -1,3 +1,5 @@
build/bin
node_modules
frontend/dist
data

67
app.go
View file

@ -3,14 +3,19 @@ package main
import (
"context"
"git.andreafazzi.eu/andrea/probo/client"
"git.andreafazzi.eu/andrea/probo/models"
"git.andreafazzi.eu/andrea/probo/store/file"
"github.com/fsnotify/fsnotify"
"github.com/wailsapp/wails/v2/pkg/runtime"
)
// App struct
type App struct {
ctx context.Context
store *file.FileProboCollectorStore
watcher *fsnotify.Watcher
}
type Quiz struct {
@ -34,6 +39,68 @@ func (a *App) startup(ctx context.Context) {
a.ctx = ctx
}
func (a *App) onShutdown(ctx context.Context) {
a.watcher.Close()
}
func (a *App) onDomReady(ctx context.Context) {
var err error
// Create new watcher.
a.watcher, err = fsnotify.NewWatcher()
if err != nil {
runtime.LogFatal(ctx, err.Error())
}
// Start listening for events.
go func() {
runtime.LogDebug(ctx, "Filesystem watcher initialized...")
for {
select {
case event, ok := <-a.watcher.Events:
if !ok {
return
}
runtime.LogDebugf(ctx, "Event: %v", event)
if event.Has(fsnotify.Write) {
runtime.LogDebugf(ctx, "Modified file: %v", event.Name)
runtime.EventsEmit(ctx, "fsChangeEvent")
}
case err, ok := <-a.watcher.Errors:
if !ok {
return
}
runtime.LogDebugf(ctx, "Error: %v", err)
}
}
}()
// Add a path.
runtime.LogDebugf(ctx, "Begin watching path %v", a.store.Dir)
err = a.watcher.Add(a.store.Dir)
if err != nil {
runtime.LogFatalf(ctx, "Error: %v", err)
}
}
func (a *App) ReadAllQuizzes() ([]*models.Quiz, error) {
return a.store.ReadAllQuizzes()
}
func (a *App) MarkdownFromQuiz(quiz *models.Quiz) (string, error) {
return file.MarkdownFromQuiz(quiz)
}
func (a *App) QuizFromMarkdown(markdown string) (*client.Quiz, error) {
return file.QuizFromMarkdown(markdown)
}
func (a *App) UpdateQuiz(quiz *client.Quiz, id string) (*models.Quiz, error) {
return a.store.UpdateQuiz(&client.CreateUpdateQuizRequest{Quiz: quiz}, id)
}
func (a *App) CreateQuiz(quiz *client.Quiz) (*models.Quiz, error) {
return a.store.CreateQuiz(&client.CreateUpdateQuizRequest{Quiz: quiz})
}

View file

@ -1,6 +0,0 @@
Per intensità di corrente elettrica si intende
* La quantità di carica che scorre in un circuito per unità di tempo
* La differenza di potenziale elettrico
* La quantità di carica elettrica
* L'accelerazione a cui sono sottoposti gli elettroni all'interno del circuito

View file

@ -1,6 +0,0 @@
L'amperometro è
* Uno strumento di misura della corrente elettrica
* Uno strumento di misura della differenza di potenziale
* Si collega al circuito in parallelo
* Uno strumento di misura della carica elettrostatica

View file

@ -1,6 +0,0 @@
Il voltmetro
* Serve a misurare la differenza di potenziale tra due punti del circuito
* Serve a misurare la corrente elettrica circolante tra due punti del circuito
* Misura la carica elettrostatica presente sulle armature di un condensatore
* E' sempre collegato in serie

View file

@ -1,6 +0,0 @@
La corrente elettrica si misura in
* Ampere (A)
* Volt (V)
* Coulomb (C)
* Metri al secondo (m/s)

View file

@ -1,6 +0,0 @@
Un generatore all'interno di un circuito elettrico ha la funzione di
* Mantenere le cariche in movimento mantenendo stabile una differenza di potenziale
* Generare energia dal nulla
* Rallentare le cariche che altrimenti si muoverebbero troppo velocemente
* Nessuna delle risposte previste è corretta

View file

@ -1,6 +0,0 @@
La prima legge di Ohm descrive la relazione tra
* Corrente elettrica e differenza di potenziale
* Tra potenza dissipata e corrente elettrica
* Tra capacità di un condensatore e differenza di potenziale
* Tra potenziale gravitazionale e corrente elettrica

View file

@ -1,6 +0,0 @@
La resistenza elettrica R
* E' pari al rapporto tra differenza di potenziale e corrente elettrica e si misura in ohm
* E' pari al rapporto tra differenza di potenziale e forza elettromotrice e si misura in volt
* E' pari al prodotto tra differenza di potenziale e corrente elettrica e si misura in ohm
* E' pari al rapporto tra differenza di potenziale e potenza dissipata e si misura in joule

View file

@ -1,6 +0,0 @@
Il cosidetto effetto Joule
* Descrive la potenza dissipata in un circuito sotto forma di calore
* Descrive la quantità di calore dissipata dal circuito
* Descrive la differenza di potenziale presente nel circuito
* Nessuna delle risposte previste è corretta

View file

@ -1,6 +0,0 @@
La seconda legge di Ohm
* Mette in relazione la resistenza di un conduttore con la sua lunghezza e la sua sezione
* Mette in relazione la differenza di potenziale con la corrente elettrica
* Non esiste
* Descrive la quantità di calore dissipata da un circuito per effetto Joule

View file

@ -1,6 +0,0 @@
Affinché in un circuito possa circolare corrente continua, il generatore di tensione
* Fornisce una potenza pari a quella dissipata per effetto Joule
* Fornisce una potenza minore a quella dissipata per effetto Joule
* Rallenta le cariche elettriche
* Dev'essere spento

View file

@ -1,6 +0,0 @@
Se in un circuito l'energia non venisse dissipata per effetto Joule
* Una volta attivata la circolazione della corrente elettrica, le cariche continuerebbero a muoversi indefinitamente
* Le cariche continuerebbero a muoversi indefinitamente senza necessità di attivare la loro circolazione
* Allora sarebbe dissipata sotto forma di calore
* Le cariche si fermerebbero immediatamente

View file

@ -1,7 +0,0 @@
Quali grandezze fisiche mette in relazione l'esperienza di Oersted?
* Campo magnetico con campo elettrico
* Campo gravitazionale con campo elettrico
* L'energia nucleare forte con quella debole
* Cariche elettriche con densità di carica superficiale

View file

@ -1,6 +0,0 @@
La corrente elettrica rappresenta
* Un moto ordinato di cariche elettriche
* Un moto disordinato di cariche elettriche
* Un moto disordinato di masse
* Un moto ordinato di masse

View file

@ -1,9 +1,48 @@
<script lang="ts">
import { models } from "$lib/wailsjs/go/models"
import { models, client } from "$lib/wailsjs/go/models"
import { QuizFromMarkdown, MarkdownFromQuiz, UpdateQuiz } from "$lib/wailsjs/go/main/App"
export let quiz:models.Quiz
import { tick } from 'svelte';
let editing = false;
let content: string;
let textarea: HTMLTextAreaElement;
async function handleKeyDown(event) {
if (event.key === 'Escape') {
editing = false;
try {
const clientQuiz: client.Quiz = await QuizFromMarkdown(content)
await UpdateQuiz(clientQuiz, quiz.id)
} catch (error) {
console.log("An error occurred:", error)
}
}
}
async function handleEdit() {
editing = true;
window.addEventListener('keydown', handleKeyDown);
await tick();
MarkdownFromQuiz(quiz).then((result:string) => content = result)
textarea.focus();
}
function handleBlur() {
editing = false;
window.removeEventListener('keydown', handleKeyDown);
}
</script>
<div class="card p-4 my-3 mx-4 font-heading-token">
<div on:click={handleEdit} class="card p-4 my-3 mx-4 font-heading-token">
{#if editing}
<textarea class="textarea" rows="20" bind:this={textarea} bind:value={content} on:blur={handleBlur}></textarea>
{:else}
<header class="p-3">
<div class="flex justify-between">
<div>
@ -23,15 +62,21 @@
</header>
<hr class="opacity-50" />
<p class="p-4">
{quiz.question.Text}
{quiz.question.text}
</p>
<form id="form-id-{quiz.id}">
<div class="space-y-2 p-4">
{#each quiz.answers as answer}
<label class="flex items-center space-x-2">
<input class="radio" type="radio" checked name="radio-direct" value="{answer.ID}" />
<p>{answer.Text}</p>
<input class="radio" type="radio" checked name="radio-direct" value="{quiz.correct.id}" />
<p>{quiz.correct.text}</p>
</label>
{#each quiz.answers as answer}
{#if answer.id != quiz.correct.id}
<label class="flex items-center space-x-2">
<input class="radio" type="radio" name="radio-direct" value="{answer.id}" />
<p>{answer.text}</p>
</label>
{/if}
{/each}
</div>
</form>
@ -46,4 +91,5 @@
<small>Created on {new Date().toLocaleDateString()}</small>
</div>
</footer>
{/if}
</div>

View file

@ -1,5 +1,14 @@
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
import {client} from '../models';
import {models} from '../models';
export function CreateQuiz(arg1:client.Quiz):Promise<models.Quiz>;
export function MarkdownFromQuiz(arg1:models.Quiz):Promise<string>;
export function QuizFromMarkdown(arg1:string):Promise<client.Quiz>;
export function ReadAllQuizzes():Promise<Array<models.Quiz>>;
export function UpdateQuiz(arg1:client.Quiz,arg2:string):Promise<models.Quiz>;

View file

@ -2,6 +2,22 @@
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
export function CreateQuiz(arg1) {
return window['go']['main']['App']['CreateQuiz'](arg1);
}
export function MarkdownFromQuiz(arg1) {
return window['go']['main']['App']['MarkdownFromQuiz'](arg1);
}
export function QuizFromMarkdown(arg1) {
return window['go']['main']['App']['QuizFromMarkdown'](arg1);
}
export function ReadAllQuizzes() {
return window['go']['main']['App']['ReadAllQuizzes']();
}
export function UpdateQuiz(arg1, arg2) {
return window['go']['main']['App']['UpdateQuiz'](arg1, arg2);
}

View file

@ -1,13 +1,104 @@
export namespace client {
export class Question {
text: string;
static createFrom(source: any = {}) {
return new Question(source);
}
constructor(source: any = {}) {
if ('string' === typeof source) source = JSON.parse(source);
this.text = source["text"];
}
}
export class Answer {
text: string;
correct: boolean;
static createFrom(source: any = {}) {
return new Answer(source);
}
constructor(source: any = {}) {
if ('string' === typeof source) source = JSON.parse(source);
this.text = source["text"];
this.correct = source["correct"];
}
}
export class Quiz {
question?: Question;
answers: Answer[];
static createFrom(source: any = {}) {
return new Quiz(source);
}
constructor(source: any = {}) {
if ('string' === typeof source) source = JSON.parse(source);
this.question = this.convertValues(source["question"], Question);
this.answers = this.convertValues(source["answers"], Answer);
}
convertValues(a: any, classs: any, asMap: boolean = false): any {
if (!a) {
return a;
}
if (a.slice) {
return (a as any[]).map(elem => this.convertValues(elem, classs));
} else if ("object" === typeof a) {
if (asMap) {
for (const key of Object.keys(a)) {
a[key] = new classs(a[key]);
}
return a;
}
return new classs(a);
}
return a;
}
}
}
export namespace models {
export class Answer {
id: string;
text: string;
static createFrom(source: any = {}) {
return new Answer(source);
}
constructor(source: any = {}) {
if ('string' === typeof source) source = JSON.parse(source);
this.id = source["id"];
this.text = source["text"];
}
}
export class Question {
id: string;
text: string;
answer_ids: string[];
static createFrom(source: any = {}) {
return new Question(source);
}
constructor(source: any = {}) {
if ('string' === typeof source) source = JSON.parse(source);
this.id = source["id"];
this.text = source["text"];
this.answer_ids = source["answer_ids"];
}
}
export class Quiz {
id: string;
hash: string;
// Go type: Question
question?: any;
question?: Question;
answers: Answer[];
// Go type: Answer
correct?: any;
correct?: Answer;
type: number;
static createFrom(source: any = {}) {
@ -18,9 +109,9 @@ export namespace models {
if ('string' === typeof source) source = JSON.parse(source);
this.id = source["id"];
this.hash = source["hash"];
this.question = this.convertValues(source["question"], null);
this.question = this.convertValues(source["question"], Question);
this.answers = this.convertValues(source["answers"], Answer);
this.correct = this.convertValues(source["correct"], null);
this.correct = this.convertValues(source["correct"], Answer);
this.type = source["type"];
}

View file

@ -1,18 +1,70 @@
<script lang="ts">
import QuizCard from "$lib/components/QuizCard.svelte"
import { EventsOn, LogDebug } from "$lib/wailsjs/runtime/runtime"
import { invalidateAll } from "$app/navigation";
import { tick } from 'svelte';
import { client } from "$lib/wailsjs/go/models"
import { QuizFromMarkdown, CreateQuiz } from "$lib/wailsjs/go/main/App"
export let data;
let creatingNewQuiz: boolean
let newQuizContent: string
let textarea: HTMLTextAreaElement;
EventsOn("fsChangeEvent", async () => {
try {
await invalidateAll()
LogDebug("Reload from disk")
} catch (error) {
console.log("An error occurred:", error)
}
})
async function handleKeyDown(event) {
if (event.key === 'Escape') {
creatingNewQuiz = false;
try {
const clientQuiz: client.Quiz = await QuizFromMarkdown(newQuizContent)
await CreateQuiz(clientQuiz)
} catch (error) {
console.log("An error occurred:", error)
throw error
}
}
}
function handleBlur() {
creatingNewQuiz = false;
window.removeEventListener('keydown', handleKeyDown);
}
async function handleEdit() {
creatingNewQuiz = true;
window.addEventListener('keydown', handleKeyDown);
await tick();
textarea.focus();
}
</script>
<div class="h-full flex flex-col">
<button type="button" class="btn sticky top-0 mt-4 mx-4 variant-ghost-secondary">
{#if creatingNewQuiz}
<div class="card p-4 my-3 mx-4 font-heading-token">
<textarea bind:this={textarea} bind:value={newQuizContent} on:blur={handleBlur} class="textarea" rows="20"></textarea>
</div>
{:else}
<button on:click={handleEdit} type="button" class="btn sticky top-0 mt-4 mx-4 variant-ghost-secondary">
<span><svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 9v6m3-3H9m12 0a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
</span>
<span>Add a new quiz</span>
</button>
{/if}
{#each data.quizzes as quiz}
<QuizCard {quiz} />
{/each}

View file

@ -16,7 +16,8 @@ const config = {
// See https://kit.svelte.dev/docs/adapters for more information about adapters.
adapter: adapter({
fallback: 'index.html'
})
}),
files: { lib: './src/lib/' }
}
};
export default config;

5
go.mod
View file

@ -3,7 +3,8 @@ module changeme
go 1.18
require (
git.andreafazzi.eu/andrea/probo v0.0.0-20230707161509-7a1135de0fd3
git.andreafazzi.eu/andrea/probo v0.0.0-20230713044636-dd4636a89dd0
github.com/fsnotify/fsnotify v1.6.0
github.com/wailsapp/wails/v2 v2.5.1
)
@ -29,7 +30,7 @@ require (
github.com/valyala/fasttemplate v1.2.2 // indirect
github.com/wailsapp/mimetype v1.4.1 // indirect
golang.org/x/crypto v0.11.0 // indirect
golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df // indirect
golang.org/x/exp v0.0.0-20230711153332-06a737ee72cb // indirect
golang.org/x/net v0.12.0 // indirect
golang.org/x/sys v0.10.0 // indirect
golang.org/x/text v0.11.0 // indirect

11
go.sum
View file

@ -1,10 +1,12 @@
git.andreafazzi.eu/andrea/probo v0.0.0-20230707161509-7a1135de0fd3 h1:Ldb0JVvfbvWLyaJfPefWIy85I1c2GO1dPOSVg/vcVMw=
git.andreafazzi.eu/andrea/probo v0.0.0-20230707161509-7a1135de0fd3/go.mod h1:lv4LRymK1SeOIs3Y1nJDA/8Ps/fRHQJDbIR+4Tdxtm4=
git.andreafazzi.eu/andrea/probo v0.0.0-20230713044636-dd4636a89dd0 h1:WrR000VnRjLXbdRDz61NBzoUpJGdGTD2TRgubCxEoks=
git.andreafazzi.eu/andrea/probo v0.0.0-20230713044636-dd4636a89dd0/go.mod h1:lv4LRymK1SeOIs3Y1nJDA/8Ps/fRHQJDbIR+4Tdxtm4=
github.com/bep/debounce v1.2.1 h1:v67fRdBA9UQu2NhLFXrSg0Brw7CexQekrBwDMM8bzeY=
github.com/bep/debounce v1.2.1/go.mod h1:H8yggRPQKLUhUoqrJC1bO2xNya7vanpDl7xR3ISbCJ0=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
@ -65,8 +67,8 @@ github.com/wailsapp/wails/v2 v2.5.1 h1:mfG+2kWqQXYOwdgI43HEILjOZDXbk5woPYI3jP2b+
github.com/wailsapp/wails/v2 v2.5.1/go.mod h1:jbOZbcr/zm79PxXxAjP8UoVlDd9wLW3uDs+isIthDfs=
golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA=
golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio=
golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df h1:UA2aFVmmsIlefxMk29Dp2juaUSth8Pyn3Tq5Y5mJGME=
golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
golang.org/x/exp v0.0.0-20230711153332-06a737ee72cb h1:xIApU0ow1zwMa2uL1VDNeQlNVFTWMQxZUZCMDy0Q4Us=
golang.org/x/exp v0.0.0-20230711153332-06a737ee72cb/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50=
golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA=
@ -79,6 +81,7 @@ golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA=
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=

View file

@ -28,6 +28,8 @@ func main() {
},
BackgroundColour: &options.RGBA{R: 27, G: 38, B: 54, A: 1},
OnStartup: app.startup,
OnDomReady: app.onDomReady,
OnShutdown: app.onShutdown,
Bind: []interface{}{
app,
},