18 Commits
v0.2.0 ... main

Author SHA1 Message Date
8294edc1d1 Annotation sequence in editor skips unused in set
Signed-off-by: Justin Georgi <justin.georgi@gmail.com>
2025-05-05 21:28:11 -07:00
846106a1a8 Fix: View menu width on mobile
Signed-off-by: Justin Georgi <justin.georgi@gmail.com>
2025-05-04 09:47:59 -07:00
717ef152f1 Fix: Successfully delete final annotation
Signed-off-by: Justin Georgi <justin.georgi@gmail.com>
2025-05-03 20:35:23 -07:00
859183fe2e Add disable hs buttons when 0 annotations
Signed-off-by: Justin Georgi <justin.georgi@gmail.com>
2025-05-03 20:04:00 -07:00
9953dff4a0 Add better reset image
Signed-off-by: Justin Georgi <justin.georgi@gmail.com>
2025-05-03 11:28:55 -07:00
8ed4e1f679 Fix: reset after annotation selection
Signed-off-by: Justin Georgi <justin.georgi@gmail.com>
2025-05-03 11:26:02 -07:00
7e353bee24 Add reset view position button to model menu
Signed-off-by: Justin Georgi <justin.georgi@gmail.com>
2025-05-03 10:02:43 -07:00
383818b6f8 Fix: annotation drag
Signed-off-by: Justin Georgi <justin.georgi@gmail.com>
2025-05-03 07:59:39 -07:00
88cd5e4727 Fix: set initial view for current selected view
Signed-off-by: Justin Georgi <justin.georgi@gmail.com>
2025-05-02 21:03:41 -07:00
a2246015c9 Merge pull request 'Convert configs from json to toml' (#58) from toml_annot into main
Reviewed-on: #58
2025-05-02 03:54:19 +00:00
840b5e34df Update readme
Signed-off-by: Justin Georgi <justin.georgi@gmail.com>
2025-05-01 20:47:33 -07:00
9857e77a1a Restore preview edit functions
Signed-off-by: Justin Georgi <justin.georgi@gmail.com>
2025-05-01 20:16:29 -07:00
ea51793e2f Un-fix: extension.json
Signed-off-by: Justin Georgi <justin.georgi@gmail.com>
2025-05-01 19:49:26 -07:00
5561038c25 Add TOML handling to JS modules
Signed-off-by: Justin Georgi <justin.georgi@gmail.com>
2025-05-01 19:18:04 -07:00
95647069eb Read toml formatted configs for display
Signed-off-by: Justin Georgi <justin.georgi@gmail.com>
2025-04-30 09:00:44 -07:00
4c11d44918 Bump version to 0.2.1
Signed-off-by: Justin Georgi <justin.georgi@gmail.com>
2025-04-02 15:41:09 -07:00
996a62f55a Improve rad to deg handling
Signed-off-by: Justin Georgi <justin.georgi@gmail.com>
2025-04-02 15:39:29 -07:00
99b84272c6 Add configuration descriptions to ReadMe 2025-03-25 20:57:25 -07:00
11 changed files with 1293 additions and 52 deletions

View File

@@ -1,3 +1,47 @@
# GlModelViewer
MediaWiki extension for using model-viewer to display and annotate 3D models
MediaWiki extension for using model-viewer to display and annotate 3D models
## Metadata
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.
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.
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 (`[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" |
| **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 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

@@ -3,7 +3,7 @@
"author": "Justin Georgi",
"url": "https://gitea.azgeorgis.net/jgeorgi/mwModelViewer",
"description": "This extension allows .glb and .gltf files to be added, displayed, and annotated in MediaWiki",
"version": "0.2.0",
"version": "0.2.1",
"license-name": "MIT",
"type": "media",
"manifest_version": 2,
@@ -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;
@@ -180,10 +183,10 @@ class GlModelTransformOutput extends MediaTransformOutput {
$attrModelView = array_merge(['src' => $srcUrl, 'class' => 'mv-model', 'interpolation-decay' => '100', 'interaction-prompt' => 'none'], $attrModelView);
$attrModelView['style'] = 'width: 100%; height: 100%;';
$attrModelView['onload'] = 'modelLoaded(event)';
$hotspotHtml = (isset($hotspots)) ? implode($hotspots) : '';
$hotspotHtml = (!empty($hotspots)) ? implode($hotspots) : '';
$elModel = Html::rawElement('model-viewer', $attrModelView, $hotspotHtml);
$elMenu = self::buildViewMenu();
$elMenu = self::buildViewMenu(!empty($hotspots));
$elFileLink = '';
if (!isset($viewParams['preview']) && $context->getTitle() != $this->file->getTitle()) {
@@ -210,9 +213,10 @@ class GlModelTransformOutput extends MediaTransformOutput {
/**
* Build the button menu used for viewer actions
*
* @param bool $annotsDrawn true to enable the annotation buttons
* @return string
*/
private static function buildViewMenu() {
private static function buildViewMenu($annotsDrawn) {
$attrMenu = array(
'class' => 'glmv-menu awaiting-model',
'style' => 'display: none;'
@@ -227,13 +231,15 @@ class GlModelTransformOutput extends MediaTransformOutput {
$gotoUrl = $mainConfig->get( 'ExtensionAssetsPath' ) . '/GlModelViewer/resources/goto_hs.svg';
$attrMenuButtonPrev = array (
'class' => 'glmv-menu-button prev-hs disable-on-hide',
'class' => 'glmv-menu-button prev-hs disable-on-hide disable-on-none',
'disabled' => !$annotsDrawn,
'onclick' => 'prevAnnotation(event.target.closest(".glmv-container").querySelector("model-viewer"))',
'onmousedown' => 'event.stopPropagation()',
'ontouchstart' => 'event.stopPropagation()'
);
$attrMenuButtonNext = array (
'class' => 'glmv-menu-button next-hs disable-on-hide',
'class' => 'glmv-menu-button next-hs disable-on-hide disable-on-none',
'disabled' => !$annotsDrawn,
'onclick' => 'nextAnnotation(event.target.closest(".glmv-container").querySelector("model-viewer"))',
'onmousedown' => 'event.stopPropagation()',
'ontouchstart' => 'event.stopPropagation()'
@@ -241,7 +247,8 @@ class GlModelTransformOutput extends MediaTransformOutput {
$slideUrl = $mainConfig->get( 'ExtensionAssetsPath' ) . '/GlModelViewer/resources/hs_slideshow.svg';
$attrMenuButtonSlides = array (
'class' => 'glmv-menu-button disable-on-hide',
'class' => 'glmv-menu-button disable-on-hide disable-on-none',
'disabled' => !$annotsDrawn,
'onclick' => 'event.target.toggleAttribute("toggled"); slideshowAnnotations(event.target.closest(".glmv-container").querySelector("model-viewer"))',
'onmousedown' => 'event.stopPropagation()',
'ontouchstart' => 'event.stopPropagation()'
@@ -249,7 +256,8 @@ class GlModelTransformOutput extends MediaTransformOutput {
$hideUrl = $mainConfig->get( 'ExtensionAssetsPath' ) . '/GlModelViewer/resources/hs_hide.svg';
$attrMenuButtonHide = array (
'class' => 'glmv-menu-button',
'class' => 'glmv-menu-button disable-on-none',
'disabled' => !$annotsDrawn,
'onclick' => 'event.target.toggleAttribute("toggled"); toggleAnnotations(event.target.closest(".glmv-container").querySelector("model-viewer"))',
'onmousedown' => 'event.stopPropagation()',
'ontouchstart' => 'event.stopPropagation()'
@@ -264,12 +272,21 @@ class GlModelTransformOutput extends MediaTransformOutput {
'ontouchstart' => 'event.stopPropagation()'
);
$resetUrl = $mainConfig->get( 'ExtensionAssetsPath' ) . '/GlModelViewer/resources/reset.svg';
$attrMenuButtonReset = array (
'class' => 'glmv-menu-button',
'onclick' => 'resetView(event.target.closest(".glmv-container").querySelector("model-viewer"))',
'onmousedown' => 'event.stopPropagation()',
'ontouchstart' => 'event.stopPropagation()'
);
$menuButtons = array(
Html::rawElement('div', $attrMenuButtonPrev, '<img class="awaiting-model" src="' . $gotoUrl . '"></image>'),
Html::rawElement('div', $attrMenuButtonSlides, '<img class="awaiting-model" src="' . $slideUrl . '"></image>'),
Html::rawElement('div', $attrMenuButtonNext, '<img class="awaiting-model" src="' . $gotoUrl . '"></image>'),
Html::rawElement('div', $attrMenuButtonHide, '<img class="awaiting-model" src="' . $hideUrl . '"></image>'),
Html::rawElement('div', $attrMenuButtonScreen, '<img class="awaiting-model full-hide" src="' . $screenUpUrl . '"></image><img class="awaiting-model full-show" src="' . $screenDownUrl . '"></image>')
Html::rawElement('div', $attrMenuButtonScreen, '<img class="awaiting-model full-hide" src="' . $screenUpUrl . '"></image><img class="awaiting-model full-show" src="' . $screenDownUrl . '"></image>'),
Html::rawElement('div', $attrMenuButtonReset, '<img class="awaiting-model" src="' . $resetUrl . '"></image>')
);
return Html::rawElement('div', $attrMenu, $menuImg . implode($menuButtons));

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()
@@ -67,13 +69,15 @@ clickDeleteHotspot = function (hs) {
deleteHotspot = null
enableViewer()
const anName = hs.target.childNodes[0].innerText
let purgeAnnotation = new RegExp('(?<="annotationSets"[\\S\\s]*?)(^.*?' + anName + '.*\n)','gm')
hs.target.remove()
const editText = $('#wpTextbox1').val()
const newText = editText.replace(purgeAnnotation,'')
const finalText = newText.replace(/(,)(\n\s+])/gm,'$2')
$('#wpTextbox1').val(finalText)
writeMvconfig()
let currentText = $('#wpTextbox1').val()
let [_, mvconfig] = extractMvconfig(currentText)
delete mvconfig.annotations[anName]
for (anSet in mvconfig.annotationSets) {
mvconfig.annotationSets[anSet]=mvconfig.annotationSets[anSet].filter( x => x !== anName )
}
let newText = currentText.replace(/(.*?<mvconfig>)[\S\s]*?(<\/mvconfig>.*)/,`$1\n${TOML.stringify(mvconfig, null, 2)}\n$2`)
$('#wpTextbox1').val(newText)
readMvconfig()
}
@@ -91,7 +95,7 @@ isDeleting = function() {
*/
grabAnnotation = function(e) {
if (e.ctrlKey) {
grabHotspot = {x: e.x, y: e.y}
grabHotspot = {x: e.x, y: e.y, target: e.target}
const contEl = $('.glmv-container')[0]
contEl.addEventListener('mousemove', moveAnnotation)
const mvEl = $('model-viewer')[0]
@@ -108,7 +112,7 @@ grabAnnotation = function(e) {
moveAnnotation = function(e) {
if (grabHotspot) {
grabHotspot.move = true
e.target.style['transform'] = `translate(${e.x - grabHotspot.x}px, ${e.y - grabHotspot.y}px) scale(1.1,1.1)`
grabHotspot.target.style['transform'] = `translate(${e.x - grabHotspot.x}px, ${e.y - grabHotspot.y}px) scale(1.1,1.1)`
}
}
@@ -147,7 +151,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: {
@@ -17,6 +19,12 @@ extractMvconfig = function() {
if (mvconfig.annotations === undefined) {
mvconfig.annotations = {}
}
const mView = $('model-viewer')[0]
const hsButtons = [...mView.parentElement.querySelectorAll('.disable-on-none')]
hsButtons.forEach( mb => {
mb.toggleAttribute('disabled',$.isEmptyObject(mvconfig.annotations))
})
if (mvconfig.annotationSets === undefined) {
mvconfig.annotationSets = {}
}
@@ -24,7 +32,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
@@ -34,7 +42,7 @@ readMvconfig = function() {
let mvconfig
let slotNum = 1
createHotspot = function(hsLabel, hsSlot, hsTag) {
createHotspot = function(hsLabel, hsSlot, hsTag, hsSkip) {
let newHs = document.createElement('button')
newHs.classList.add('Hotspot')
newHs.setAttribute('slot',`hotspot-${hsSlot}`)
@@ -42,6 +50,7 @@ readMvconfig = function() {
newHs.setAttribute('onclick', 'onAnnotation(event)')
newHs.setAttribute('onmousedown', 'grabAnnotation(event)')
newHs.setAttribute('onmouseup', 'releaseAnnotation(event)')
newHs.toggleAttribute('seq-skip', !!hsSkip)
Object.keys(mvconfig.annotations[hsLabel]).forEach((prop) => {
newHs.setAttribute(prop, mvconfig.annotations[hsLabel][prop])
})
@@ -73,7 +82,7 @@ readMvconfig = function() {
return
}
let label = (currentSet != 'default' && mvconfig.annotationSets[currentSet]) ? '-' : null
createHotspot(hs, slotNum, label)
createHotspot(hs, slotNum, label, true)
slotNum += 1
})
@@ -90,8 +99,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 +118,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
}
@@ -135,8 +144,9 @@ orb2degree = function(orbString, fix = null) {
}
if (fix) {
degArray = degArray.map((v, idx) => {
let fixReg = new RegExp('(\\d*.\\d{' + (((typeof fix) == 'object') ? fix[idx] : fix) + '})(\\d*)([a-z]*)')
return v.replace(fixReg,'$1$3')
//let fixReg = new RegExp('(-?\\d*\\.\\d{' + (((typeof fix) == 'object') ? fix[idx] : fix) + '})(\\d*)([a-z]*)')
//return v.replace(fixReg,'$1$3')
return Number.parseFloat(v).toFixed((typeof fix == 'object') ? fix[idx] : fix) + v.substring(v.search(/[a-z]/))
})
}
return degArray.join(' ')
@@ -157,7 +167,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
}
@@ -171,7 +181,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)
}
@@ -185,7 +195,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)
}
@@ -235,6 +245,7 @@ selectViewConfig = function(view) {
mView.removeAttribute(s)
}
})
mView.setAttribute('current-view',selectView)
}
/**
@@ -243,6 +254,7 @@ selectViewConfig = function(view) {
*/
writeCameraOrbit = function() {
const mView = $('model-viewer')[0]
const currentView = mView.getAttribute('current-view') ? mView.getAttribute('current-view') : 'default'
const newOrbit = orb2degree(mView.getCameraOrbit().toString(),[2,2,5])
mView.setAttribute('camera-orbit', newOrbit)
const targetObj = mView.getCameraTarget()
@@ -251,10 +263,10 @@ writeCameraOrbit = function() {
const newField = mView.getFieldOfView().toFixed(5) + 'deg'
mView.setAttribute('field-of-view',newField)
let [currentText, mvconfig] = extractMvconfig()
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`)
mvconfig.viewerConfig[currentView]['camera-orbit'] = newOrbit
mvconfig.viewerConfig[currentView]['camera-target'] = newTarget
mvconfig.viewerConfig[currentView]['field-of-view'] = newField
const textUpdate = currentText.replace(/(?<=<mvconfig>)([\S\s]*?)(?=<\/mvconfig>)/gm,`\n${TOML.stringify(mvconfig, null, 2)}\n`)
$('#wpTextbox1').val(textUpdate)
}
@@ -276,6 +288,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)
}

View File

@@ -117,7 +117,7 @@
justify-content: flex-start;
&:hover {
width: 196px;
width: 232px;
& .glmv-menu-image {
transform: rotate(180deg);
@@ -209,7 +209,7 @@
border-radius: 9px;
&:hover {
width: 294px;
width: 342px;
}
}

View File

@@ -72,7 +72,7 @@ selectAnnotation = function(mView, annotId) {
*/
nextAnnotation = function(mView) {
let incrAnnotation = 0
const numSpots = [...mView.querySelectorAll('Button')].length
const numSpots = [...mView.querySelectorAll('Button:not([seq-skip])')].length
const currentAnnotation = mView.querySelectorAll('Button:has(.HotspotAnnotation:not(.HiddenAnnotation))')[0]
if (!currentAnnotation) {
incrAnnotation = 1
@@ -95,7 +95,7 @@ nextAnnotation = function(mView) {
*/
prevAnnotation = function(mView) {
let decrAnnotation = 0
const numSpots = [...mView.querySelectorAll('Button')].length
const numSpots = [...mView.querySelectorAll('Button:not([seq-skip])')].length
const currentAnnotation = mView.querySelectorAll('Button:has(.HotspotAnnotation:not(.HiddenAnnotation))')[0]
if (!currentAnnotation) {
decrAnnotation = numSpots
@@ -158,4 +158,18 @@ toggleFullScreen = function(glCont) {
} else {
glCont.requestFullscreen()
}
}
/**
* Reset view to initial position
*
* @param {ModelViewer} mView
*/
resetView = function(mView) {
const resetOrb = mView.getAttribute('camera-orbit') || 'auto auto auto'
const resetTarg = mView.getAttribute('camera-target') || 'auto auto auto'
const resetFov = mView.getAttribute('field-of-view') || 'auto'
mView.cameraOrbit = resetOrb
mView.cameraTarget = resetTarg
mView.fieldOfView = resetFov
}

1150
modules/mini-st.js Normal file

File diff suppressed because it is too large Load Diff

1
resources/reset.svg Normal file
View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#5f6368"><path d="M320-280q-33 0-56.5-23.5T240-360v-240q0-33 23.5-56.5T320-680h40l40-40h160l40 40h40q33 0 56.5 23.5T720-600v240q0 33-23.5 56.5T640-280H320Zm0-80h320v-240H320v240Zm160-40q33 0 56.5-23.5T560-480q0-33-23.5-56.5T480-560q-33 0-56.5 23.5T400-480q0 33 23.5 56.5T480-400ZM342-940q34-11 68.5-15.5T480-960q94 0 177.5 33.5t148 93Q870-774 911-693.5T960-520h-80q-7-72-38-134.5t-79.5-110Q714-812 651-842t-135-36l62 62-56 56-180-180ZM618-20Q584-9 549.5-4.5T480 0q-94 0-177.5-33.5t-148-93Q90-186 49-266.5T0-440h80q8 72 38.5 134.5t79 110Q246-148 309-118t135 36l-62-62 56-56L618-20ZM480-480Z"/></svg>

After

Width:  |  Height:  |  Size: 696 B