Merge pull request 'Convert configs from json to toml' (#58) from toml_annot into main

Reviewed-on: #58
This commit is contained in:
2025-05-02 03:54:19 +00:00
8 changed files with 1203 additions and 33 deletions

View File

@@ -4,30 +4,44 @@ MediaWiki extension for using model-viewer to display and annotate 3D models
## Metadata
Model-viewer configurations and annotations can be added as a json string in the file's description.
Model-viewer configurations and annotations can be added as TOML in the file's description.
### Viewer Configuration
Configuration of the model-viewer instance is contained in the `viewerConfig` object.
Configuration of the model-viewer instance is contained in the `[viewerConfig]` object.
This object can contain any number of child objects each of which describes a different possible configuration.
Each configuration can have properties matching the any of the attributes of the model-viewer element except for `src`, `class`, `interpolation-delay`, and `interaction-prompt`.
If adding a poster image, the value of the `poster` property must be the name of an image in the Wiki's File namespace (without the namespace header, e.g., `"My poster.jpg"`).
### Annotations
Annotations can be configured using the `annotations` object.
Annotations can be configured using the `[annotations]` object.
This object can contain any number of child objects each of which describes a different possible annotation.
The label for each annotation is the name of the child object. Each object has two required properties and two optional properties.
The label for each annotation is the name of the child object (`[annotations."Label here"]`). Each object has two required properties and three optional properties.
| Property | Required | Description | Example |
|----------|----------|-------------|---------|
| `data-position` | Yes | String containing the X, Y, and Z coordinates of the annotation. | "0.02297m -0.02544m 0.01780m" |
| `data-normal` | Yes | String containing the X, Y, and Z coordinates of the normal vector of the annotation. | "0.78966m -0.53116m 0.30709m" |
| `data-orbit` | No | String containing the orbit vector of the camera to view annotation. | "2.31rad 2.01rad 0.65m" |
| `data-target` | No | String containing the X, Y, and Z coordinates of the camera target to view annotation. | "-0.01029m 0.00071m 0.01138m" |
| **data-position** | Yes | String containing the X, Y, and Z coordinates of the annotation. | "0.02297m -0.02544m 0.01780m" |
| **data-normal** | Yes | String containing the X, Y, and Z coordinates of the normal vector of the annotation. | "0.78966m -0.53116m 0.30709m" |
| **data-orbit** | No | String containing the orbit vector of the camera to view annotation. | "2.31rad 2.01rad 0.65m" |
| **data-target** | No | String containing the X, Y, and Z coordinates of the camera target to view annotation. | "-0.01029m 0.00071m 0.01138m" |
| **field-of-view** | No | String containing the angle of the field of view in degrees. | "35.25deg" |
### Annotation sets
Annotations sets are specific subsets of the listed annotations that can be shown on the model.
They are configured using the `annotationSets` object.
This object can contain any number of child objects each of which contains an array of strings matching the names of the annotation to be included.
They are configured using the `[annotationSets]` object.
This object can contain any number properties each of which contains an array of strings matching the names of the annotation to be included.
The property name is used as the annotation set identifier.
By default, all annotations are rendered on a model.
## Linking
When adding the model file to a page, use the standard `[[File:Model name.glb]]` internal file link.
The link supports three optional properties.
| Property | Description | Default |
|----------|-------------|---------|
| **view** | Use one of the non-default configurations specified in the `[viewerConfig]` object. | default viewer configuration |
| **set** | Display only the annotations listed in the specified annotation set from the `[annotationSets]` object. | show all annotations |
| **transform** | Reorder the displayed annotations. Can be either *rand* or *alpha*. | annotations are numbered in the order they are listed in the `[annotations]` object |

5
composer.json Normal file
View File

@@ -0,0 +1,5 @@
{
"require": {
"devium/toml": "*"
}
}

View File

@@ -74,6 +74,7 @@
],
"packageFiles": [
"glmv-prev.js",
"mini-st.js",
"glmv-mvconfig.js",
"glmv-hs.js"
]

View File

@@ -44,7 +44,7 @@ class GlModelHooks {
}
/**
* Render the config json in a <pre> tag
* Render the config toml in a <pre> tag
*
* @param $input The text inside the custom tag
* @param array $args Any attributes given in the tag
@@ -170,15 +170,8 @@ class GlModelHooks {
$uploadFormObj->mComment .= <<<CONF
<mvconfig>
{
"viewerConfig": {
"default": {
"camera-controls": true
}
},
"annotations": {},
"annotationSets": {}
}
[viewerConfig.default]
camera-controls = true
</mvconfig>
CONF;
}

View File

@@ -1,6 +1,9 @@
<?php
namespace MediaWiki\Extension\GlModelViewer;
require_once __DIR__ . '/../vendor/autoload.php';
use Devium\Toml\Toml;
use MediaWiki\MediaWikiServices;
use MediaTransformOutput;
use ConfigFactory;
@@ -49,7 +52,7 @@ class GlModelTransformOutput extends MediaTransformOutput {
public function toHtml($options = []) {
$descriptText = $this->file->getDescriptionText();
preg_match('/<pre mvconfig.*?>([\S\s]*?)<\/pre>/',$descriptText,$modelDescript);
$metadata = json_decode($modelDescript[1], true);
$metadata = toml_decode($modelDescript[1], true);
if ($this->thumb) {
$poster = $metadata['viewerConfig'][$this->view]['poster'] ?? false;

View File

@@ -1,3 +1,5 @@
const TOML = require('./mini-st.js')
let deleteHotspot = null
let grabHotspot = null
@@ -34,7 +36,7 @@ clickAddHotspot = function(e) {
hsOutput['data-target'] = `${targetObj.x.toFixed(5)}m ${targetObj.y.toFixed(5)}m ${targetObj.z.toFixed(5)}m`
hsOutput['field-of-view'] = Number.parseFloat(targetModel.getFieldOfView()).toFixed(5) + 'deg'
mvconfig.annotations['Hotspot ' + (Object.keys(mvconfig.annotations).length + 1)] = hsOutput
let newText = currentText.replace(/(.*?<mvconfig>)[\S\s]*?(<\/mvconfig>.*)/,`$1\n${JSON.stringify(mvconfig, null, 2)}\n$2`)
let newText = currentText.replace(/(.*?<mvconfig>)[\S\s]*?(<\/mvconfig>.*)/,`$1\n${TOML.stringify(mvconfig, null, 2)}\n$2`)
$('#wpTextbox1').val(newText)
}
readMvconfig()
@@ -147,7 +149,7 @@ releaseAnnotation = function(e) {
"data-target": newTarg,
"field-of-view": newFov
}
const newText = currentText.replace(/(.*?<mvconfig>)[\S\s]*?(<\/mvconfig>.*)/,`$1\n${JSON.stringify(mvconfig, null, 2)}\n$2`)
const newText = currentText.replace(/(.*?<mvconfig>)[\S\s]*?(<\/mvconfig>.*)/,`$1\n${TOML.stringify(mvconfig, null, 2)}\n$2`)
$('#wpTextbox1').val(newText)
}
grabHotspot = null

View File

@@ -1,3 +1,5 @@
const TOML = require('./mini-st.js')
/**
* Convert text in the preview text editor to js object
*
@@ -6,7 +8,7 @@
extractMvconfig = function() {
const editText = $('#wpTextbox1').val()
const extractConfig = editText.match(/<mvconfig>([\S\s]*?)<\/mvconfig>/)
let mvconfig = (extractConfig.length >= 2) ? JSON.parse(extractConfig[1]) : {viewerConfig: {}, annotations: {}, annotationSets: {}}
let mvconfig = (extractConfig.length >= 2) ? TOML.parse(extractConfig[1]) : {viewerConfig: {}, annotations: {}, annotationSets: {}}
if (mvconfig.viewerConfig === undefined) {
mvconfig.viewerConfig = {
default: {
@@ -24,7 +26,7 @@ extractMvconfig = function() {
}
/**
* Reads the json string in the edit panel
* Reads the TOML string in the edit panel
* and updates hotspot elements and menu settings
*
* @return {bool|object} arrays of view and set names on successful read and update false on failure
@@ -90,8 +92,8 @@ readMvconfig = function() {
}
/**
* Parses the current hotspots into json object
* and writes the json string to the edit panel
* Parses the current hotspots into TOML object
* and writes the TOML string to the edit panel
*
* @return {bool} true on successful write to edit panel
*/
@@ -109,7 +111,7 @@ writeMvconfig = function () {
if (Object.keys(annotationsObj).length === 0) return false
const [currentText, mvconfig] = extractMvconfig()
mvconfig.annotations = annotationsObj
const newText = currentText.replace(/(.*?<mvconfig>)[\S\s]*?(<\/mvconfig>.*)/,`$1\n${JSON.stringify(mvconfig, null, 2)}\n$2`)
const newText = currentText.replace(/(.*?<mvconfig>)[\S\s]*?(<\/mvconfig>.*)/,`$1\n${TOML.stringify(mvconfig, null, 2)}\n$2`)
$('#wpTextbox1').val(newText)
return true
}
@@ -158,7 +160,7 @@ toggleCameraControl = function(view) {
} else {
delete mvconfig.viewerConfig[currentView]['camera-controls']
}
const textUpdate = currentText.replace(/(?<=<mvconfig>)([\S\s]*?)(?=<\/mvconfig>)/gm,`\n${JSON.stringify(mvconfig, null, 2)}\n`)
const textUpdate = currentText.replace(/(?<=<mvconfig>)([\S\s]*?)(?=<\/mvconfig>)/gm,`\n${TOML.stringify(mvconfig, null, 2)}\n`)
$('#wpTextbox1').val(textUpdate)
return newControl
}
@@ -172,7 +174,7 @@ addAnnotationSet = function(newSet) {
const mView = $('model-viewer')[0]
let [currentText, mvconfig] = extractMvconfig()
mvconfig.annotationSets[newSet] = []
const textUpdate = currentText.replace(/(?<=<mvconfig>)([\S\s]*?)(?=<\/mvconfig>)/gm,`\n${JSON.stringify(mvconfig, null, 2)}\n`)
const textUpdate = currentText.replace(/(?<=<mvconfig>)([\S\s]*?)(?=<\/mvconfig>)/gm,`\n${TOML.stringify(mvconfig, null, 2)}\n`)
$('#wpTextbox1').val(textUpdate)
selectAnnotationSet(newSet)
}
@@ -186,7 +188,7 @@ addViewConfig = function(newView) {
const mView = $('model-viewer')[0]
let [currentText, mvconfig] = extractMvconfig()
mvconfig.viewerConfig[newView] = { "camera-controls": true }
const textUpdate = currentText.replace(/(?<=<mvconfig>)([\S\s]*?)(?=<\/mvconfig>)/gm,`\n${JSON.stringify(mvconfig, null, 2)}\n`)
const textUpdate = currentText.replace(/(?<=<mvconfig>)([\S\s]*?)(?=<\/mvconfig>)/gm,`\n${TOML.stringify(mvconfig, null, 2)}\n`)
$('#wpTextbox1').val(textUpdate)
selectViewConfig(newView)
}
@@ -255,7 +257,7 @@ writeCameraOrbit = function() {
mvconfig.viewerConfig.default['camera-orbit'] = newOrbit
mvconfig.viewerConfig.default['camera-target'] = newTarget
mvconfig.viewerConfig.default['field-of-view'] = newField
const textUpdate = currentText.replace(/(?<=<mvconfig>)([\S\s]*?)(?=<\/mvconfig>)/gm,`\n${JSON.stringify(mvconfig, null, 2)}\n`)
const textUpdate = currentText.replace(/(?<=<mvconfig>)([\S\s]*?)(?=<\/mvconfig>)/gm,`\n${TOML.stringify(mvconfig, null, 2)}\n`)
$('#wpTextbox1').val(textUpdate)
}
@@ -277,6 +279,6 @@ writeCameraLimit = function(axis, limit) {
oldOrbitVals[valueIndex] = newOrbitVals[valueIndex]
mvconfig.viewerConfig.default[`${limit}-camera-orbit`] = oldOrbitVals.join(' ')
mView.setAttribute(`${limit}-camera-orbit`, oldOrbitVals.join(' '))
const textUpdate = currentText.replace(/(?<=<mvconfig>)([\S\s]*?)(?=<\/mvconfig>)/gm,`\n${JSON.stringify(mvconfig, null, 2)}\n`)
const textUpdate = currentText.replace(/(?<=<mvconfig>)([\S\s]*?)(?=<\/mvconfig>)/gm,`\n${TOML.stringify(mvconfig, null, 2)}\n`)
$('#wpTextbox1').val(textUpdate)
}

1150
modules/mini-st.js Normal file

File diff suppressed because it is too large Load Diff