Skip to content

CLDR-17441 show keyman keyboard #3262

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
114 changes: 114 additions & 0 deletions tools/cldr-apps/js/src/esm/cldrKeyboard.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
/**
* Operations around keyboard support
*/

import * as cldrClient from "./cldrClient.mjs";
import * as cldrStatus from "./cldrStatus.mjs";

// See https://keyman.com/developer/keymanweb/ for the latest version and scripts to use
const KEYMAN_VERSION = "17.0.333";
const KEYMAN_SCRIPTS = ["keymanweb.js", "kmwuitoggle.js"];
const KEYMAN_BASE = "https://s.keyman.com/kmw/engine/";
const KEYMAN_ATTACH = `(
function(kmw) {
kmw.init({attachType:'auto'});
}
)(keyman);`;


// are the scripts installed?
let installed = false;
let installing = false;
// is the option enabled?
let enabled = false;
// did we call init yet?
let initted = false;


function getRemoteScript(src) {
const e = document.createElement('script');
e.setAttribute('src', src);
return e;
}

function getInlineScript(body) {
const e = document.createElement('script');
e.textContent = body;
return e;
}

/** insert the script tags */
async function installKeyboards() {
if (installing) return;
installing = true;

if (!installed) {
const { body } = document;
for (const script of KEYMAN_SCRIPTS) {
const e = getRemoteScript(`${KEYMAN_BASE}${KEYMAN_VERSION}/${script}`);
body.appendChild(e);
await waitForLoad(e); // block here until loaded
}
const attach = getInlineScript(KEYMAN_ATTACH);
body.appendChild(attach);
installed = true;
installing = false;
updateKeyboardLocale(cldrStatus.getCurrentLocale());
}

function waitForLoad(e) {
return new Promise((resolve, reject) => {
try {
e.addEventListener('load', resolve);
} catch (err) {
reject(err);
}
});
}
}

/**
* Update whether user has requested web keyboards
* @param {boolean} enable true if keyboards are enabled
*/
export function setUseKeyboards(enable) {
if (enable != enabled) {
if (enable) {
installKeyboards().then(() => { enabled = true; });
} else {
enabled = false;
}
}
}

/**
* Update the keyboard locale
* @param {string} curLocale
*/
export function updateKeyboardLocale(curLocale) {
if (installed && enabled) {
// make sure keyman is loaded
for (const { InternalName } of keyman.getKeyboards()) {
keyman.removeKeyboards(InternalName);
console.log(`Removed kbd: ${InternalName}`);
}
console.log(`Adding kbd: @${curLocale}`);
keyman.addKeyboards(`@${curLocale}`);
// end keyboards
}
}

export function isEnabled() {
return enabled;
}

/** Note: may need to call reload() in order to unload keyboard. */
export async function setEnabledPref(enable) {
const client = await cldrClient.getClient();
setUseKeyboards(enable);
if (enable) {
await client.apis.user.setSetting({setting: 'webkeyboard'});
} else {
await client.apis.user.removeSetting({setting: 'webkeyboard'});
}
}
3 changes: 3 additions & 0 deletions tools/cldr-apps/js/src/esm/cldrMenu.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import * as cldrCoverage from "./cldrCoverage.mjs";
import * as cldrDom from "./cldrDom.mjs";
import * as cldrEvent from "./cldrEvent.mjs";
import * as cldrGui from "./cldrGui.mjs";
import * as cldrKeyboard from "./cldrKeyboard.mjs";
import * as cldrLoad from "./cldrLoad.mjs";
import { LocaleMap } from "./cldrLocaleMap.mjs";
import * as cldrStatus from "./cldrStatus.mjs";
Expand Down Expand Up @@ -418,6 +419,8 @@ function updateLocaleMenu() {
const curLocale = cldrStatus.getCurrentLocale();
let prefixMessage = "";
if (curLocale != null && curLocale != "" && curLocale != "-") {
// hook to update the keyboard locale
cldrKeyboard.updateKeyboardLocale(curLocale);
const locmap = cldrLoad.getTheLocaleMap();
cldrStatus.setCurrentLocaleName(locmap.getLocaleName(curLocale));
var bund = locmap.getLocaleInfo(curLocale);
Expand Down
4 changes: 4 additions & 0 deletions tools/cldr-apps/js/src/esm/cldrStatus.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
*/
import * as cldrGui from "./cldrGui.mjs";
import { ref } from "vue";
import { setUseKeyboards } from "./cldrKeyboard.mjs";

const refs = {
currentLocale: ref(null),
Expand Down Expand Up @@ -44,6 +45,7 @@ function getStatusTarget() {
* Events:
* - sessionId: session ID changed
* - surveyUser: survey user changed
* - update: any update changed
*/
function on(type, callback) {
getStatusTarget().addEventListener(type, callback);
Expand Down Expand Up @@ -94,6 +96,8 @@ function updateAll(status) {
if (status.user) {
setSurveyUser(status.user);
}
setUseKeyboards(status?.settings?.user?.webkeyboard);
dispatchEvent(new Event('update'));
}

/**
Expand Down
11 changes: 11 additions & 0 deletions tools/cldr-apps/js/src/views/MainMenu.vue
Original file line number Diff line number Diff line change
Expand Up @@ -134,11 +134,15 @@
<li><a href="#error_subtypes///">Error Subtypes</a></li>
</ul>
</li>
<li v-if="loggedIn">
<a-checkbox v-model:checked="webkeyboard" @change="setKeyboard">Show Web Keyboard</a-checkbox>
</li>
</ul>
</template>

<script>
import * as cldrStatus from "../esm/cldrStatus.mjs";
import * as cldrKeyboard from "../esm/cldrKeyboard.mjs";

export default {
data() {
Expand All @@ -158,6 +162,7 @@ export default {
uploadXmlUrl: null,
userId: 0,
showClaMenu: true,
webkeyboard: false,
};
},

Expand Down Expand Up @@ -187,6 +192,12 @@ export default {
this.org = cldrStatus.getOrganizationName();
this.recentActivityUrl = this.getSpecialUrl("recent_activity");
this.uploadXmlUrl = this.getSpecialUrl("upload");
cldrStatus.on('update', () => this.webkeyboard = cldrKeyboard.isEnabled());
},

setKeyboard(checked) {
cldrKeyboard.setEnabledPref(this.webkeyboard);
// window.location.reload();
},

getSpecialUrl(special) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
import java.util.Set;
import java.util.TreeSet;
import java.util.logging.Logger;
import org.json.JSONException;
import org.json.JSONObject;
import org.unicode.cldr.util.CLDRConfig;
import org.unicode.cldr.util.Level;
import org.unicode.cldr.util.StandardCodes;
Expand Down Expand Up @@ -937,4 +939,16 @@ public String getMessage() {
public void setMessage(String s) {
sessionMessage = s;
}

/* get any extra settings for the client as a JSONObject, or null */
public JSONObject getSettingsJSON() throws JSONException {
// right now, only user settings, but could expose others
if (user == null) return null;
final JSONObject userSettings = user.settings().getClientJSON();
if (userSettings != null) {
return new JSONObject().put("user", userSettings);
} else {
return null;
}
}
}
61 changes: 36 additions & 25 deletions tools/cldr-apps/src/main/java/org/unicode/cldr/web/SurveyMain.java
Original file line number Diff line number Diff line change
Expand Up @@ -3697,6 +3697,7 @@ private class StatusForFrontEnd implements JSONString {
private User user = null;
private final int users = CookieSession.getUserCount();
private String sessionMessage = null;
private JSONObject settings = null;

private final Runtime r = Runtime.getRuntime();
double memtotal = r.totalMemory() / 1024000.0;
Expand All @@ -3708,31 +3709,36 @@ private JSONObject toJSONObject() throws JSONException {
* Doc for JSONObject.put(string, object), says: "It [object] should be of one of these types:
* Boolean, Double, Integer, JSONArray, JSONObject, Long, String, or the JSONObject.NULL object."
*/
return new JSONObject()
.put("contextPath", contextPath)
.put("dbopen", dbopen)
.put("dbused", dbused)
.put("observers", observers)
.put("isBusted", isBusted)
.put("isPhaseBeta", isPhaseBeta)
.put("isSetup", isSetup)
.put("isUnofficial", isUnofficial)
.put("newVersion", newVersion)
.put("organizationName", organizationName)
.put("pages", pages)
.put("permissions", permissions)
.put("phase", phase)
.put("extendedPhase", extendedPhase)
.put("sessionId", sessionId)
.put("sessionMessage", sessionMessage)
.put("specialHeader", specialHeader)
.put("surveyRunningStamp", surveyRunningStamp)
.put("sysload", sysload)
.put("memtotal", memtotal)
.put("memfree", memfree)
.put("uptime", uptime)
.put("user", user) // allowed since User implements JSONString?
.put("users", users);
final JSONObject j =
new JSONObject()
.put("contextPath", contextPath)
.put("dbopen", dbopen)
.put("dbused", dbused)
.put("observers", observers)
.put("isBusted", isBusted)
.put("isPhaseBeta", isPhaseBeta)
.put("isSetup", isSetup)
.put("isUnofficial", isUnofficial)
.put("newVersion", newVersion)
.put("organizationName", organizationName)
.put("pages", pages)
.put("permissions", permissions)
.put("phase", phase)
.put("extendedPhase", extendedPhase)
.put("sessionId", sessionId)
.put("sessionMessage", sessionMessage)
.put("specialHeader", specialHeader)
.put("surveyRunningStamp", surveyRunningStamp)
.put("sysload", sysload)
.put("memtotal", memtotal)
.put("memfree", memfree)
.put("uptime", uptime)
.put("user", user) // allowed since User implements JSONString?
.put("users", users);
if (settings != null) {
j.put("settings", settings);
}
return j;
}

@Override
Expand Down Expand Up @@ -3767,6 +3773,11 @@ private void setSessionIdAndUser(HttpServletRequest request) {
sessionId = mySession.id;
user = mySession.user;
sessionMessage = mySession.getMessage();
try {
settings = mySession.getSettingsJSON();
} catch (JSONException j) {
SurveyLog.logException(logger, j, "getting user session for " + sessionId);
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import org.json.JSONException;
import org.json.JSONObject;

public abstract class UserSettings implements Comparable<UserSettings> {
/**
Expand Down Expand Up @@ -103,4 +105,25 @@ public <T> T getJson(String name, Class<T> clazz) {
if (j == null || j.isBlank()) return null;
return gson.fromJson(j, clazz);
}

// enum with the names of client visible settings
public enum ClientVisibleSettings {
// enable a web keyboard (Keyman)
webkeyboard,
};

public JSONObject getClientJSON() throws JSONException {
JSONObject j = new JSONObject();
for (final ClientVisibleSettings s : ClientVisibleSettings.values()) {
final String v = get(s.name(), null);
if (v != null) {
j.put(s.name(), v);
}
}
if (j.length() > 0) {
return j;
} else {
return null;
}
}
}
Loading
Loading