diff --git a/README.md b/README.md index 298458929..4686049ad 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,294 @@ Here are some of our high-level priorities to get to a production-ready state: - Implement all types and appearances defined in [XLSForm](https://xlsform.org/en/ref-table/) - Define a thoughtful interface for host applications that balances ease of use and flexibility +Here is the feature matrix and the progress we have made so far: + + + + + +
+ ${\mathtt{Question \space \space types \space \space (basic \space \space functionality)\color{transparent}==== \color{green}███\color{LightGray}█████████████████ \space \color{initial} 15\%}}$ + +
+ +| Feature | Progress | +| -------------------------- | :------: | +| text | ✅ | +| integer | | +| decimal | | +| note | 🚧 | +| select_one | ✅ | +| select_multiple | ✅ | +| repeat | ✅ | +| group | ✅ | +| geopoint | | +| geotrace | | +| geoshape | | +| start-geopoint | | +| range | | +| image | | +| barcode | | +| audio | | +| background-audio | | +| video | | +| file | | +| date | | +| time | | +| datetime | | +| rank | | +| csv-external | | +| acknowledge | | +| start | | +| end | | +| today | | +| deviceid | | +| username | | +| phonenumber | | +| email | | +| audit | | + +
+ +
+ ${\mathtt{Appearances\color{transparent}============================= \color{green}████\color{LightGray}████████████████ \space \color{initial} 21\%}}$ + +
+ +| Feature | Progress | +| -------------------------- | :------: | +| numbers | | +| multiline | | +| url | | +| ex: | | +| thousands-sep | | +| bearing | | +| vertical | | +| no-ticks | | +| picker | | +| rating | | +| new | | +| new-front | | +| draw | | +| annotate | | +| signature | | +| no-calendar | | +| month-year | | +| year | | +| ethiopian | | +| coptic | | +| islamic | | +| bikram-sambat | | +| myanmar | | +| persian | | +| placement-map | | +| maps | | +| hide-input | | +| minimal | ✅ | +| search / autocomplete | ✅ | +| quick | | +| columns-pack | | +| columns | ✅ | +| columns-n | ✅ | +| no-buttons | ✅ | +| image-map | | +| likert | | +| map | | +| field-list | ✅ | +| label | ✅ | +| list-nolabel | ✅ | +| list | ✅ | +| table-list | | + +
+ +
+ ${\mathtt{Parameters\color{transparent}============================== \color{green}████\color{LightGray}████████████████ \space \color{initial} 22\%}}$ + +
+ +| Feature | Progress | +| -------------------------------------------------------------------------------------------------------------------------------- | :------: | +| randomize | ✅ | +| seed | ✅ | +| value | | +| label | | +| geopoint capture-accuracy, warning-accur
acy, allow-mock-accuracy | | +| range start, end, step | | +| image max-pixels | | +| audio quality | | +| Audit: location-priority, location-min-i
nterval, location-max-age, track-changes
, track-changes-reasons, identify-user | | + +
+ +
+ ${\mathtt{Form \space \space Logic\color{transparent}============================== \color{green}██████████\color{LightGray}██████████ \space \color{initial} 50\%}}$ + +
+ +| Feature | Progress | +| -------------------------- | :------: | +| calculate | ✅ | +| relevant | ✅ | +| required | ✅ | +| required message | 🚧 | +| custom constraint | 🚧 | +| constraint message | 🚧 | +| read only | ✅ | +| trigger | | +| choice filter | ✅ | +| default | ✅ | +| query parameter | | +| repeat_count | | + +
+ +
+ ${\mathtt{Descriptions \space \space and \space \space Annotations\color{transparent}============ \color{green}██\color{LightGray}██████████████████ \space \color{initial} 13\%}}$ + +
+ +| Feature | Progress | +| ---------------------------------------------- | :------: | +| label | ✅ | +| hint | | +| guidance hint | | +| Translations | ✅ | +| Translations with field/question value | | +| Markdown | | +| Inline HTML | | +| Form attachments | | +| image | | +| big-image | | +| audio | | +| video | | +| secondary instance (external choice file
) | | +| secondary instance (last saved) | | +| autoplay | | + +
+ +
+ ${\mathtt{Theme \space \space and \space \space Layouts\color{transparent}======================= \color{green}█\color{LightGray}███████████████████ \space \color{initial} 9\%}}$ + +
+ +| Feature | Progress | +| -------------------------- | :------: | +| grid | | +| pages | | +| print | | +| logo | | +| theme color | | +| Submissions | | +| preview | ✅ | +| send | | +| view | | +| edit | | +| attachments | | + +
+ +
+ ${\mathtt{Offline \space \space capabilities\color{transparent}==================== \color{green}█\color{LightGray}███████████████████ \space \color{initial} 0\%}}$ + +
+ +| Feature | Progress | +| ---------------------------- | :------: | +| List of projects & forms | | +| local persistence (single) | | +| save as draft | | +| offline entities | | +| MBtiles / offline map layers | | + +
+ +
+ ${\mathtt{XPath\color{transparent}=================================== \color{green}██████████████████\color{LightGray}██ \space \color{initial} 94\%}}$ + +
+ +| Feature | Progress | +| --------------------------------------------------------------------------------------------------------------- | :------: | +| operators | ✅ | +| predicates | ✅ | +| axes | ✅ | +| string(\* arg) | ✅ | +| concat(string arg*\|node-set arg*) | ✅ | +| join(string separator, node-set nodes\*) | ✅ | +| substr(string value, number start, numbe
r end?) | ✅ | +| substring-before(string, string) | ✅ | +| substring-after(string, string) | ✅ | +| translate(string, string, string) | ✅ | +| string-length(string arg) | ✅ | +| normalize-space(string arg?) | ✅ | +| contains(string haystack, string needle) | ✅ | +| starts-with(string haystack, string need
le) | ✅ | +| ends-with(string haystack, string needle
) | ✅ | +| uuid(number?) | ✅ | +| digest(string src, string algorithm, str
ing encoding?) | ✅ | +| pulldata(string instance_id, string desi
red_element, string query_element, strin
g query) | | +| if(boolean condition, _ then, _ else) | ✅ | +| coalesce(string arg1, string arg2) | ✅ | +| once(string calc) | ✅ | +| true() | ✅ | +| false() | ✅ | +| boolean(\* arg) | ✅ | +| boolean-from-string(string arg) | ✅ | +| not(boolean arg) | ✅ | +| regex(string value, string expression) | ✅ | +| checklist(number min, number max, string
v\*) | ✅ | +| weighted-checklist(number min, number ma
x, [string v, string w]\*) | ✅ | +| number(\* arg) | ✅ | +| random() | ✅ | +| int(number arg) | ✅ | +| sum(node-set arg) | ✅ | +| max(node-set arg\*) | ✅ | +| min(node-set arg\*) | ✅ | +| round(number arg, number decimals?) | ✅ | +| pow(number value, number power) | ✅ | +| log(number arg) | ✅ | +| log10(number arg) | ✅ | +| abs(number arg) | ✅ | +| sin(number arg) | ✅ | +| cos(number arg) | ✅ | +| tan(number arg) | ✅ | +| asin(number arg) | ✅ | +| acos(number arg) | ✅ | +| atan(number arg) | ✅ | +| atan2(number arg, number arg) | ✅ | +| sqrt(number arg) | ✅ | +| exp(number arg) | ✅ | +| exp10(number arg) | ✅ | +| pi() | ✅ | +| count(node-set arg) | ✅ | +| count-non-empty(node-set arg) | ✅ | +| position(node arg?) | ✅ | +| instance(string id) | ✅ | +| current() | ✅ | +| randomize(node-set arg, number seed) | ✅ | +| today() | ✅ | +| now() | ✅ | +| format-date(date value, string format) | ✅ | +| format-date-time(dateTime value, string
format) | ✅ | +| date(\* value) | ✅ | +| decimal-date-time(dateTime value) | ✅ | +| decimal-time(time value) | ✅ | +| selected(string list, string value) | ✅ | +| selected-at(string list, number index) | ✅ | +| count-selected(node node) | ✅ | +| jr:choice-name(node node, string value) | | +| jr:itext(string id) | ✅ | +| indexed-repeat(node-set arg, node-set re
peat1, number index1, [node-set repeatN,
number indexN]{0,2}) | | +| area(node-set ns\|geoshape gs) | ✅ | +| distance(node-set ns\|geoshape gs\|geotr
ace gt\|(geopoint\|string) arg\*) | ✅ | +| base64-decode(base64Binary input) | | + +
+ + + We welcome discussion about the project [on the ODK forum](https://forum.getodk.org/)! The forum is generally the preferred place for questions, issue reports, and feature requests unless you have information to add to an existing issue. ## Q&A diff --git a/feature-matrix.json b/feature-matrix.json new file mode 100644 index 000000000..26c65322a --- /dev/null +++ b/feature-matrix.json @@ -0,0 +1,218 @@ +{ + "Question types (basic functionality)": { + "text": "✅", + "integer": "", + "decimal": "", + "note": "🚧", + "select_one": "✅", + "select_multiple": "✅", + "repeat": "✅", + "group": "✅", + "geopoint": "", + "geotrace": "", + "geoshape": "", + "start-geopoint": "", + "range": "", + "image": "", + "barcode": "", + "audio": "", + "background-audio": "", + "video": "", + "file": "", + "date": "", + "time": "", + "datetime": "", + "rank": "", + "csv-external": "", + "acknowledge": "", + "start": "", + "end": "", + "today": "", + "deviceid": "", + "username": "", + "phonenumber": "", + "email": "", + "audit": "" + }, + "Appearances": { + "numbers": "", + "multiline": "", + "url": "", + "ex:": "", + "thousands-sep": "", + "bearing": "", + "vertical": "", + "no-ticks": "", + "picker": "", + "rating": "", + "new": "", + "new-front": "", + "draw": "", + "annotate": "", + "signature": "", + "no-calendar": "", + "month-year": "", + "year": "", + "ethiopian": "", + "coptic": "", + "islamic": "", + "bikram-sambat": "", + "myanmar": "", + "persian": "", + "placement-map": "", + "maps": "", + "hide-input": "", + "minimal": "✅", + "search / autocomplete": "✅", + "quick": "", + "columns-pack": "", + "columns": "✅", + "columns-n": "✅", + "no-buttons": "✅", + "image-map": "", + "likert": "", + "map": "", + "field-list": "✅", + "label": "✅", + "list-nolabel": "✅", + "list": "✅", + "table-list": "" + }, + "Parameters": { + "randomize": "✅", + "seed": "✅", + "value": "", + "label": "", + "geopoint capture-accuracy, warning-accuracy, allow-mock-accuracy": "", + "range start, end, step": "", + "image max-pixels": "", + "audio quality": "", + "Audit: location-priority, location-min-interval, location-max-age, track-changes, track-changes-reasons, identify-user": "" + }, + "Form Logic": { + "calculate": "✅", + "relevant": "✅", + "required": "✅", + "required message": "🚧", + "custom constraint": "🚧", + "constraint message": "🚧", + "read only": "✅", + "trigger": "", + "choice filter": "✅", + "default": "✅", + "query parameter": "", + "repeat_count": "" + }, + "Descriptions and Annotations": { + "label": "✅", + "hint": "", + "guidance hint": "", + "Translations": "✅", + "Translations with field/question value": "", + "Markdown": "", + "Inline HTML": "", + "Form attachments": "", + "image": "", + "big-image": "", + "audio": "", + "video": "", + "secondary instance (external choice file)": "", + "secondary instance (last saved)": "", + "autoplay": "" + }, + "Theme and Layouts": { + "grid": "", + "pages": "", + "print": "", + "logo": "", + "theme color": "", + "Submissions": "", + "preview": "✅", + "send": "", + "view": "", + "edit": "", + "attachments": "" + }, + "Offline capabilities": { + "List of projects & forms": "", + "local persistence (single)": "", + "save as draft": "", + "offline entities": "", + "MBtiles / offline map layers": "" + }, + "XPath": { + "operators": "✅", + "predicates": "✅", + "axes": "✅", + "string(* arg)": "✅", + "concat(string arg*|node-set arg*)": "✅", + "join(string separator, node-set nodes*)": "✅", + "substr(string value, number start, number end?)": "✅", + "substring-before(string, string)": "✅", + "substring-after(string, string)": "✅", + "translate(string, string, string)": "✅", + "string-length(string arg)": "✅", + "normalize-space(string arg?)": "✅", + "contains(string haystack, string needle)": "✅", + "starts-with(string haystack, string needle)": "✅", + "ends-with(string haystack, string needle)": "✅", + "uuid(number?)": "✅", + "digest(string src, string algorithm, string encoding?)": "✅", + "pulldata(string instance_id, string desired_element, string query_element, string query)": "", + "if(boolean condition, * then, * else)": "✅", + "coalesce(string arg1, string arg2)": "✅", + "once(string calc)": "✅", + "true()": "✅", + "false()": "✅", + "boolean(* arg)": "✅", + "boolean-from-string(string arg)": "✅", + "not(boolean arg)": "✅", + "regex(string value, string expression)": "✅", + "checklist(number min, number max, string v*)": "✅", + "weighted-checklist(number min, number max, [string v, string w]*)": "✅", + "number(* arg)": "✅", + "random()": "✅", + "int(number arg)": "✅", + "sum(node-set arg)": "✅", + "max(node-set arg*)": "✅", + "min(node-set arg*)": "✅", + "round(number arg, number decimals?)": "✅", + "pow(number value, number power)": "✅", + "log(number arg)": "✅", + "log10(number arg)": "✅", + "abs(number arg)": "✅", + "sin(number arg)": "✅", + "cos(number arg)": "✅", + "tan(number arg)": "✅", + "asin(number arg)": "✅", + "acos(number arg)": "✅", + "atan(number arg)": "✅", + "atan2(number arg, number arg)": "✅", + "sqrt(number arg)": "✅", + "exp(number arg)": "✅", + "exp10(number arg)": "✅", + "pi()": "✅", + "count(node-set arg)": "✅", + "count-non-empty(node-set arg)": "✅", + "position(node arg?)": "✅", + "instance(string id)": "✅", + "current()": "✅", + "randomize(node-set arg, number seed)": "✅", + "today()": "✅", + "now()": "✅", + "format-date(date value, string format)": "✅", + "format-date-time(dateTime value, string format)": "✅", + "date(* value)": "✅", + "decimal-date-time(dateTime value)": "✅", + "decimal-time(time value)": "✅", + "selected(string list, string value)": "✅", + "selected-at(string list, number index)": "✅", + "count-selected(node node)": "✅", + "jr:choice-name(node node, string value)": "", + "jr:itext(string id)": "✅", + "indexed-repeat(node-set arg, node-set repeat1, number index1, [node-set repeatN, number indexN]{0,2})": " ", + "area(node-set ns|geoshape gs)": "✅", + "distance(node-set ns|geoshape gs|geotrace gt|(geopoint|string) arg*)": "✅", + "base64-decode(base64Binary input)": "" + } +} diff --git a/package.json b/package.json index 80208aa81..eb3b6e9c7 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,8 @@ "bump": "changeset add", "format": "prettier -w \"**/*\" --ignore-unknown --cache", "format:checkonly": "prettier -c \"**/*\" --ignore-unknown", - "lint": "eslint . --report-unused-disable-directives" + "lint": "eslint . --report-unused-disable-directives", + "feature-matrix": "node scripts/feature-matrix/render.js" }, "dependencies": { "@changesets/changelog-github": "^0.5.0", diff --git a/scripts/feature-matrix/render.js b/scripts/feature-matrix/render.js new file mode 100644 index 000000000..46f8b2a38 --- /dev/null +++ b/scripts/feature-matrix/render.js @@ -0,0 +1,75 @@ +/* eslint-disable */ +// @ts-nocheck + +import Mustache from 'mustache'; +import fs from 'node:fs/promises'; + +const rootUrl = new URL('../..', import.meta.url); +const featureMatrix = JSON.parse( + await fs.readFile(new URL('./feature-matrix.json', rootUrl), 'utf-8') +); +const template = await fs.readFile( + new URL('scripts/feature-matrix/template.mustache', rootUrl), + 'utf-8' +); +const readmeFile = await fs.readFile(new URL('./README.md', rootUrl), 'utf-8'); + +// Modified version of https://gist.github.com/rougier/c0d31f5cbdaac27b876c?permalink_comment_id=2269298#gistcomment-2269298 +const progress = ({ value, length = 20 }) => { + const v = (value / 100) * length; + const x = v < 1 ? 1 : Math.floor(v); + const bar = Array(x).fill('█').join(''); + const remaining = Array(length - bar.length) + .fill('█') + .join(''); + return `\\color{green}${bar}\\color{LightGray}${remaining} \\space \\color{initial} ${value}\\%`; +}; + +// Not so smart, blindly breaks the word. Okay for now. +const wrapString = (str, maxLength) => { + let parts = []; + for (let i = 0; i < str.length; i += maxLength) { + parts.push(str.slice(i, i + maxLength)); + } + return parts.join('
'); +}; + +// Transform feature-matrix.json object into array +const featureCategories = Object.keys(featureMatrix).map((featureCategory) => { + const features = Object.keys(featureMatrix[featureCategory]).map((feature) => { + return { + label: wrapString(feature.replaceAll('|', '\\|'), 40), + status: featureMatrix[featureCategory][feature], + }; + }); + + // no points for 🚧 + const progressPercentage = Math.floor( + (features.filter((f) => f.status === '✅').length / features.length) * 100 + ); + + // hack: Space character in Latex monospace font is not same size as other characters. + // Using '=' sign with transparent color for padding. 🙃 + let label = `${featureCategory}`.padEnd(40, '=').replaceAll(' ', ' \\space \\space '); + label = label.replace('=', '\\color{transparent}='); + + const progressOutput = progress({ value: progressPercentage }); + + return { + // Using Latex for of collapsible - this allows using colored text in MD. + // mathtt is for monospace. + label: '${\\mathtt{' + label + ' ' + progressOutput + '}}$', + features, + }; +}); + +const featureMatrixMd = Mustache.render(template, { categories: featureCategories }); + +const autogenOpen = ''; +const autogenClose = ''; + +const regex = new RegExp(`(${autogenOpen})[\\s\\S]*?(${autogenClose})`, 'm'); + +const updatedReadme = readmeFile.replace(regex, `$1\n${featureMatrixMd}\n$2`); + +await fs.writeFile(new URL('./README.md', rootUrl), updatedReadme, { encoding: 'utf-8' }); diff --git a/scripts/feature-matrix/template.mustache b/scripts/feature-matrix/template.mustache new file mode 100644 index 000000000..9ba089b2d --- /dev/null +++ b/scripts/feature-matrix/template.mustache @@ -0,0 +1,15 @@ +{{ #categories }} + +
+ {{{ label }}} + +
+ + | Feature | Progress | + | ----------- | :----------: | + {{ #features }} + | {{{ label }}} | {{ status }} | + {{ /features }} + +
+{{ /categories }} \ No newline at end of file diff --git a/scripts/package.json b/scripts/package.json index 7142d695b..d3e73d9b9 100644 --- a/scripts/package.json +++ b/scripts/package.json @@ -7,6 +7,7 @@ "test": "true" }, "devDependencies": { - "globby": "^14.0.2" + "globby": "^14.0.2", + "mustache": "4.2.0" } } diff --git a/yarn.lock b/yarn.lock index f8c479806..cc0c05c51 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4804,6 +4804,11 @@ muggle-string@^0.4.0: resolved "https://registry.yarnpkg.com/muggle-string/-/muggle-string-0.4.1.tgz#3b366bd43b32f809dc20659534dd30e7c8a0d328" integrity sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ== +mustache@4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/mustache/-/mustache-4.2.0.tgz#e5892324d60a12ec9c2a73359edca52972bf6f64" + integrity sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ== + mute-stream@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-1.0.0.tgz#e31bd9fe62f0aed23520aa4324ea6671531e013e"