Skip to content
Snippets Groups Projects
Commit 68132d61 authored by Valentin Rigal's avatar Valentin Rigal Committed by Bastien Abadie
Browse files

UTF8 picker

parent aa0b4950
No related branches found
No related tags found
1 merge request!7UTF8 picker
Pipeline #5615 passed with warnings
.DS_Store
node_modules
/dist-lib
/dist-ext
/dist-*
# local env files
......
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Virtual keyboard UTF-8 Picker demo</title>
</head>
<body>
<div class="container">
</div>
</body>
</html>
......@@ -9,6 +9,8 @@
"ext:chromium": "web-ext run -t chromium --source-dir ./dist-ext/",
"build-ext": "vue-cli-service build --dest dist-ext",
"serve-lib": "NODE_ENV=development webpack serve --config webpack.lib.js --progress",
"serve-picker": "NODE_ENV=development webpack serve --config webpack.picker.js --progress",
"build-picker": "NODE_ENV=development webpack --config webpack.picker.js",
"build-lib": "NODE_ENV=production webpack --config webpack.lib.js",
"lint": "vue-cli-service lint"
},
......
<template>
<div>
<div v-if="characters">
<p v-if="pages && pages.length > 1">
<span>page {{ page }} of {{ pages.length }}</span>
<button type="button" v-on:click="move(-1)">previous</button>
<button type="button" v-on:click="move(1)">next</button>
</p>
<p v-for="(row, i) in pages[page - 1]" :key="i">
<button
type="button"
class="glyph"
v-for="char in row"
:key="char.code"
>
{{ char.code | displayUTF8 }}
</button>
</p>
</div>
<div v-else>Loading…</div>
</div>
</template>
<script>
import { mapState, mapActions } from "vuex";
import { UTF8_PAGE_SIZE } from "../config.js";
import { displayUTF8 } from "../helpers.js";
export default {
props: {
scriptName: {
type: String,
required: true,
},
},
data: () => ({
page: 1,
}),
computed: {
...mapState(["scripts"]),
script() {
return this.scripts[this.scriptName];
},
characters() {
return this.script && this.script.characters;
},
pages() {
// Returns pages of 8 rows containing 16 characters each
if (!this.characters) return;
return this.characters.reduce((pages, char, index) => {
const { rows, columns } = UTF8_PAGE_SIZE;
const pageSize = rows * columns;
// Fill pages
const pageIndex = Math.floor(index / pageSize);
if (!pages[pageIndex]) pages[pageIndex] = [];
// Fill rows in the page
const rowIndex = Math.floor((index % pageSize) / columns);
if (!pages[pageIndex][rowIndex]) pages[pageIndex][rowIndex] = [];
pages[pageIndex][rowIndex].push(char);
return pages;
}, []);
},
},
methods: {
...mapActions(["retrieveScript"]),
move(direction) {
const newPage = this.page + direction;
if (newPage < 1 || newPage > this.pages.length) return;
this.page = newPage;
},
},
filters: {
displayUTF8,
},
watch: {
scriptName: {
immediate: true,
async handler(value) {
this.page = 1;
if (value) await this.retrieveScript(value);
},
},
scriptsList(scripts) {
// Automatically select the first script
if (this.scripts && this.scripts.length) this.selectedScript = scripts[0];
},
},
};
</script>
<style scoped>
button.glyph {
width: 2rem;
height: 2.5rem;
margin: 0.1rem;
}
</style>
<template>
<div>
<h3>UTF8 Picker</h3>
<select>
<option>Nyiakeng Puachue Hmong</option>
<label>UTF8 Script</label>
<select v-model="selectedScript">
<option v-if="scriptsList === null" disabled selected>
Loading glyphs…
</option>
<template v-else>
<option v-for="script in scriptsList" :key="script">
{{ script }}
</option>
</template>
</select>
<UTF8Grid v-if="selectedScript" :scriptName="selectedScript" />
</div>
</template>
<script>
export default {};
import { mapState, mapActions } from "vuex";
import UTF8Grid from "./UTF8Grid.vue";
export default {
components: {
UTF8Grid,
},
data: () => ({
selectedScript: null,
}),
async created() {
await this.listScripts();
},
computed: {
...mapState(["scriptsList"]),
},
methods: {
...mapActions(["listScripts"]),
},
watch: {
scriptsList(scripts) {
if (scripts && scripts.length) this.selectedScript = scripts[0];
},
},
};
</script>
// Number of UTF-8 characher displayed by page
export const UTF8_PAGE_SIZE = {
rows: 8,
columns: 16,
};
export const KEYBOARDS_LOCALSTORAGE_KEY = "virtual_keyboards";
export const UTF8_ASSETS_URL =
"https://assets.teklia.com/virtual-keyboard/scripts";
export const DEFAULT_KEYBOARDS = {
demo: {
version: "0.1",
name: "demo",
author: "Teklia <team@teklia.com>",
default: true,
characters: [
{
row: 0,
column: 0,
character: "\u01FB",
keyboard_code: 97,
},
{
row: 0,
column: 2,
character: "\u0235",
keyboard_code: 98,
},
{
row: 0,
column: 3,
character: "\u022A",
keyboard_code: null,
},
{
row: 1,
column: 1,
character: "\u0240",
keyboard_code: null,
},
],
},
};
import Vue from "vue";
import UTF8Picker from "../components/UTF8Picker.vue";
import store from "../store";
const picker = document.createElement("div");
document.getElementsByClassName("container")[0].appendChild(picker);
new Vue({
store,
render: (h) => {
return h(UTF8Picker, {
props: {},
});
},
el: picker,
});
export const displayUTF8 = (code) => {
return String.fromCodePoint(parseInt(code, 16));
};
import Vue from "vue";
import Vuex from "vuex";
import axios from "axios";
import {
KEYBOARDS_LOCALSTORAGE_KEY,
DEFAULT_KEYBOARDS,
UTF8_ASSETS_URL,
} from "../config";
Vue.use(Vuex);
const KEYBOARDS_KEY = "virtual_keyboards";
const defaultKeyboards = {
demo: {
version: "0.1",
name: "demo",
author: "Teklia <team@teklia.com>",
default: true,
characters: [
{
row: 0,
column: 0,
character: "\u01FB",
keyboard_code: 97,
},
{
row: 0,
column: 2,
character: "\u0235",
keyboard_code: 98,
},
{
row: 0,
column: 3,
character: "\u022A",
keyboard_code: null,
},
{
row: 1,
column: 1,
character: "\u0240",
keyboard_code: null,
},
],
},
};
// TODO Handle sync with the local storage from the store
const keyboardFromStorage = () => {
try {
const keyboards = JSON.parse(localStorage.getItem(KEYBOARDS_KEY));
return keyboards || defaultKeyboards;
const keyboards = JSON.parse(
localStorage.getItem(KEYBOARDS_LOCALSTORAGE_KEY)
);
return keyboards || DEFAULT_KEYBOARDS;
} catch {
return defaultKeyboards;
return DEFAULT_KEYBOARDS;
}
};
export default new Vuex.Store({
state: {
keyboards: keyboardFromStorage(),
scriptsList: null,
scripts: {},
},
mutations: {
setScriptsList(state, data) {
state.scriptsList = data;
},
setScript(state, { scriptName, data }) {
// Override scripts objects to trigger reactivity
state.scripts = {
...state.scripts,
[scriptName]: data,
};
},
},
actions: {
async listScripts({ state, commit }) {
// List the names of all available UTF-8 glyphs
if (state.scriptsList !== null) return;
const resp = await axios.get(`${UTF8_ASSETS_URL}/index.json`);
commit("setScriptsList", resp.data);
},
async retrieveScript({ state, commit }, scriptName) {
/*
* Retrieve a complete set of glyphs for a specific script
* Note that scripts are compressed using gzip
*/
if (state.scripts[scriptName]) return;
const resp = await axios.get(`${UTF8_ASSETS_URL}/${scriptName}.json`);
commit("setScript", { scriptName, data: resp.data });
},
},
mutations: {},
actions: {},
modules: {},
getters: {},
});
const path = require("path");
const VueLoaderPlugin = require("vue-loader/lib/plugin");
const CompressionPlugin = require("compression-webpack-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const webpack = require("webpack");
const nodeEnv = process.env.NODE_ENV || "development";
const surgeBuild = process.env.SURGE_BUILD;
const devMode = nodeEnv === "development";
const config = {
mode: nodeEnv,
entry: {
content: path.join(__dirname, "src/content/picker.js"),
},
devtool: devMode ? "cheap-module-eval-source-map" : undefined,
devServer: {
port: 8080,
historyApiFallback: true,
open: true,
overlay: true,
hot: true,
},
output: {
filename: "[name].js",
path: path.resolve(__dirname, "./dist-picker/"),
publicPath: "",
},
plugins: [
new MiniCssExtractPlugin(),
new VueLoaderPlugin(),
new CompressionPlugin({
test: /\.(js|css)$/,
}),
new HtmlWebpackPlugin({
template: path.join(__dirname, "demo", "picker.html"),
inject: true,
minify: devMode
? false
: {
removeComments: true,
collapseWhitespace: true,
removeAttributeQuotes: true,
},
}),
],
module: {
rules: [
{ test: /\.css$/, use: [MiniCssExtractPlugin.loader, "css-loader"] },
{
test: /\.(png|jpe?g|gif|woff|woff2|eot|ttf|otf|svg)$/i,
use: ["file-loader"],
},
{ test: /\.vue$/, use: ["vue-loader"] },
],
},
resolve: {
alias: {
vue$: "vue/dist/vue",
},
},
};
if (devMode) {
config.plugins.push(new webpack.HotModuleReplacementPlugin());
} else if (surgeBuild) {
// Building for Surge: do not use the version number but a hash to prevent cache issues
config.output.filename = "keyboard-[hash].js";
config.output.chunkFilename = "keyboard-[name]-[hash].js";
}
module.exports = config;
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment