memorio
An app for practicing test questions
The app offers an effective way for practicing test questions of different types (single-choice, multiple-choice, images identification, etc.). The main features are:
- Progress tracking β the app tracks and calculates success rates for all questions, categories and packages. Offers individualized practice tests (concentrate on questions with low success rates). Progress synchronization across devices. Requires login.
- Responsive UI β works on mobile and desktop devices.
- Markdown and LaTeX β questions can contain Markdown and LaTeX.
- Question editor β currently only for admin users.
- Does NOT work offline (yet) β for the original version with full offline support (but no progress
synchronization across devices) see
π this branch. - It is a PWA (Progressive web app) β runs in browser (no installation needed), works on mobile and desktop devices and can be also added to the OS home screen / app launcher for better standalone usage.
The code is written in TypeScript and React.js. See more in the Architecture section.
Content
Description
The app offers an effective way for practicing test questions of different types (single-choice, multiple-choice, images identification, etc.). Currently, only multiple-choice questions are implemented.
The content is divided by topic into packages. Each package contains one or more categories. Categories group similar questions. Contents of packages and categories can be browsed.
The app tracks user's progress across packages, categories and questions. It calculates the ratio between the correct and wrong answers. The scores can be reset at any time in the settings.
The app features practice mode in which users can practice answering the questions of a selected package. Within the package, a set of specific categories can se selected for practicing. During the practice, the questions can be sorted in three different ways depending on user's preferences:
- by progress (the questions with the lowest success rate first)
- randomly
- by order (questions' number)
Furthermore, the answers are checked continuously and the score is immediately updated. Upon an answer validation, a sound is played (to audibly indicate correct vs wrong answers). The sound effects can be enabled/disabled in the settings.
Upon first opening of the package, all its data are automatically downloaded for future offline usage. The downloaded content can be used even when the app is offline. All app data can be purged at any time in the settings.
The UI of the app is fully translated and localized into Czech and English. By default, the app automatically detects the locale to be used from the browser preferences. In addition, the locale can be changed manually in the app settings.
Architecture
Currently, it is a client-side-only application (SPA). It runs completely in the browser, and it uses Firebase's Cloud Firestore database to store all data.
The code is written in TypeScript and React.js.
The project has just a few production dependencies. Everything else is implemented from scratch.
Technology highlights
Tooling:
- webpack β custom configs, custom plugins
- Babel
- ESLint, AVA (tests), GitHub Actions (CI/CD)
- Netlify for Deployment
- CSS written in Sass (SCSS), vendor prefixes automatically added by Autoprefixer with Browserslist
Features:
- Service Worker, Cache Storage, Local Storage, IndexedDB
- full offline support (app-shell precache + content downloading)
- [Subresource Integrity (SRI)][] for all scripts and styles included from index.html
- PWA, web app manifest including maskable icons
- custom routing solution for React on top of the History API
- custom state management for React (with persistence to Local Storage)
- i18, l10n
- Intl API (time, numbers, plurals), see for example the LocalizedDate component
- advanced ICU messages, see for example this message
- automatic locale detection/negotiation (
navigator.languages
) with support for overwriting the locale on the app-level, see LocaleLoader component
- CSS
- HTML5 semantic elements
- audio effects (HTML5 Audio), see SoundPlayer
Project structure
The source code is in the app directory. Some directories contain feature-specific READMEs. The following diagram briefly describes the main directories and files:
. (project root dir)
βββ .github - GitHub config (GitHub Actions)
βββ app - the app source code
β βββ components - common React components
β βββ containers
β β βββ LocaleLoader - locale loader
β β βββ PageRouter - top-level app specific routing component
β β βββ Root - root component
β βββ data - Firestore queries wrappers and hooks
β βββ data - Firebase integration wrappers
β βββ forms-experimental - a custom solution for forms in React (not used)
β βββ helpers - core functions for different things
β βββ i18n - translations files
β βββ images - mainly the PWA app icon
β βββ router - a custom routing solution
β βββ sounds - a simple sound player and sound assets
β βββ store - a custom app state management solution backed by localStorage
β βββ styles - app styles written in Sass (SCSS)
β βββ sw - the service worker that handles precaching app shell
β βββ text - Markdown and LaTeX processing
β βββ views - pages
β β βββ CategoryPage.tsx - category contents browsing
β β βββ HomePage.tsx - packages listing
β β βββ NotFoundPage.tsx - 404
β β βββ PackagePage.tsx - package contents browsing
β β βββ PracticePage.tsx - questions practice
β β βββ QuestionPage.tsx - question editor
β β βββ SettingsPage.tsx - app settings form (locale, sounds, data)
β βββ _headers - Netlify HTTP headers customization
β βββ _redirects - Netlify HTTP redirects/rewrites customization
β βββ index.js - the app starting point
β βββ manifest.json - a web app manifest for PWA
β βββ robots.txt
β βββ routes.ts - app routes definitions
β βββ template.ejs - index.html template to be built by Webpack
β βββ types.js - data, state and API types
βββ test - a few tests
βββ tools - custom Webpack plugins
βββ types - TypeScript declarations for non-code imports (SVG, MP3)
βββ .browserslistrc - Browserslist config
βββ .eslintrc.js - ESLint config
βββ .nvmrc - Node.js version specification for Netlify
βββ ava.config.js - AVA config
βββ babel.config.js - Babel config
βββ netlify.toml - Netlify main config
βββ package.json
βββ babel.config.js - PostCSS config
βββ tsconfig.json - main TypeScript config
βββ webpack.config.*.js - Webpack configs
βββ yarn.lock
Development
Requirements
- Node.js >=18.x
- Yarn 1.x
- You can follow this Node.js Development Setup guide.
Set up
- Install all dependencies with Yarn (run
yarn
). - You are ready to go.
- Use
yarn start
to start dev server with HMR. - Then open
http://localhost:3000/
in the browser.
Available commands
-
yarn start
β Starts a webpack development server with HMR (hot module replacement). -
yarn build
β Builds the production version and outputs todist
dir. Note: Before running an actual build,dist
dir is purged. -
yarn analyze
β Same asyarn build
but it also outputsbuild/stats.production.json
and runs webpack-bundle-analyzer CLI. -
yarn tsc
β Runs TypeScript compiler. Outputs type errors to console. -
yarn lint
β Runs ESLint. Outputs errors to console. -
yarn test
β Runs tests using AVA. -
yarn test-hot
β Runs tests using AVA in watch mode.
Deployment
Currently, I use Netlify which is practically a CDN on steroids with integrated builds. There are 3 configuration files that affect the deployment behavior:
- netlify.toml β global config
- app/_headers β HTTP headers customization (mainly for immutable files)
- app_redirects β HTTP redirects and rewrites (fallback to index.html for client-side routing)
In the future, a simple CDN might not be sufficient (in terms of features, speed, costs, ...). Then a server-rendered app with additional features might be the way.
Cloudflare has always offered state-of-the-art features on the fastest and reliable network all round the world. So, I might give a chance to the Cloudflare Workers due to its unique design.