@@ -268,6 +268,7 @@ | |||
.pnlm-load-button p { | |||
display: table-cell; | |||
vertical-align: middle; | |||
padding: 1em; | |||
} | |||
.pnlm-info-box { | |||
@@ -42,6 +42,9 @@ function Renderer(container) { | |||
var pose; | |||
var image, imageType, dynamic; | |||
var texCoordBuffer, cubeVertBuf, cubeVertTexCoordBuf, cubeVertIndBuf; | |||
// based on ISO 639-1: https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes | |||
// defaults to 'en' => english | |||
var languageCode; | |||
/** | |||
* Initialize renderer. | |||
@@ -59,16 +62,20 @@ function Renderer(container) { | |||
* @param {number} vaov - Initial vertical angle of view. | |||
* @param {number} voffset - Initial vertical offset angle. | |||
* @param {function} callback - Load callback function. | |||
* @param {Object} [params] - Other configuration parameters (`horizonPitch`, `horizonRoll`, `backgroundColor`). | |||
* @param {Object} [params] - Other configuration parameters (`horizonPitch`, `horizonRoll`, `backgroundColor`, `languageCode`). | |||
*/ | |||
this.init = function(_image, _imageType, _dynamic, haov, vaov, voffset, callback, params) { | |||
// set language code for localized strings | |||
// defaults to english if not set! | |||
languageCode = params.languageCode ? params.languageCode : 'en'; | |||
// Default argument for image type | |||
if (typeof _imageType === undefined) | |||
_imageType = 'equirectangular'; | |||
if (_imageType != 'equirectangular' && _imageType != 'cubemap' && | |||
_imageType != 'multires') { | |||
console.log('Error: invalid image type specified!'); | |||
console.log(STRINGS.ERROR_INVALID_IMG_TYPE[languageCode]); | |||
throw {type: 'config error'}; | |||
} | |||
@@ -225,7 +232,7 @@ function Renderer(container) { | |||
return; | |||
} else if (!gl) { | |||
console.log('Error: no WebGL support detected!'); | |||
console.log(STRINGS.ERROR_NO_WEBGL[languageCode]); | |||
throw {type: 'no webgl'}; | |||
} | |||
if (image.basePath) { | |||
@@ -248,14 +255,14 @@ function Renderer(container) { | |||
width = Math.max(image.width, image.height); | |||
maxWidth = gl.getParameter(gl.MAX_TEXTURE_SIZE); | |||
if (width > maxWidth) { | |||
console.log('Error: The image is too big; it\'s ' + width + 'px wide, but this device\'s maximum supported width is ' + maxWidth + 'px.'); | |||
console.log(STRINGS.ERROR_IMG_TOO_BIG[languageCode] + " " + STRINGS.TEXT_IMG_SUPPORTED_WIDTH[languageCode] + maxWidth + "px."); | |||
throw {type: 'webgl size error', width: width, maxWidth: maxWidth}; | |||
} | |||
} else if (imageType == 'cubemap') { | |||
width = image[0].width; | |||
maxWidth = gl.getParameter(gl.MAX_CUBE_MAP_TEXTURE_SIZE); | |||
if (width > maxWidth) { | |||
console.log('Error: The cube face image is too big; it\'s ' + width + 'px wide, but this device\'s maximum supported width is ' + maxWidth + 'px.'); | |||
console.log(STRINGS.ERROR_CUBE_FACE_TOO_BIG[languageCode] + " " + STRINGS.TEXT_IMG_SUPPORTED_WIDTH[languageCode] + maxWidth + "px."); | |||
throw {type: 'webgl size error', width: width, maxWidth: maxWidth}; | |||
} | |||
} | |||
@@ -408,7 +415,7 @@ function Renderer(container) { | |||
// Check if there was an error | |||
var err = gl.getError(); | |||
if (err !== 0) { | |||
console.log('Error: Something went wrong with WebGL!', err); | |||
console.log(STRINGS.ERROR_WEBGL[languageCode], err); | |||
throw {type: 'webgl error'}; | |||
} | |||
@@ -1186,7 +1193,7 @@ function Renderer(container) { | |||
* @private | |||
*/ | |||
function handleWebGLError1286() { | |||
console.log('Reducing canvas size due to error 1286!'); | |||
console.log(STRINGS.ERROR_WEBGL_1286[languageCode]); | |||
canvas.width = Math.round(canvas.width / 2); | |||
canvas.height = Math.round(canvas.height / 2); | |||
} | |||
@@ -36,6 +36,16 @@ function Viewer(container, initialConfig) { | |||
var _this = this; | |||
/** | |||
* make sure that language is set so strings can be translated properly | |||
* WARNING: Make sure you use a supported `languageCode`, otherwise you will see 'undefined' strings! | |||
*/ | |||
if(!initialConfig.languageCode) { | |||
console.log("WARNING: 'languageCode' was not (properly) set in the config. Defaults to English('en') now.\ | |||
If you are the author, check your config.languageCode!"); | |||
initialConfig.languageCode = 'en'; | |||
} | |||
// Declare variables | |||
var config, | |||
renderer, | |||
@@ -100,7 +110,7 @@ var defaultConfig = { | |||
hotSpotDebug: false, | |||
backgroundColor: [0, 0, 0], | |||
animationTimingFunction: timingFunction, | |||
loadButtonLabel: 'Click to\nLoad\nPanorama', | |||
loadButtonLabel: STRINGS.TEXT_CLICK_TO_LOAD[initialConfig.languageCode], | |||
draggable: true, | |||
}; | |||
@@ -276,7 +286,7 @@ function init() { | |||
origPitch = config.pitch; | |||
var i, p; | |||
if (config.type == 'cubemap') { | |||
panoImage = []; | |||
for (i = 0; i < 6; i++) { | |||
@@ -303,7 +313,7 @@ function init() { | |||
panoImage = config.panorama; | |||
} else { | |||
if (config.panorama === undefined) { | |||
anError('No panorama image was specified.'); | |||
anError(STRINGS.ERROR_NO_PANORAMA[config.languageCode]); | |||
return; | |||
} | |||
panoImage = new Image(); | |||
@@ -326,7 +336,7 @@ function init() { | |||
var a = document.createElement('a'); | |||
a.href = e.target.src; | |||
a.innerHTML = a.href; | |||
anError('The file ' + a.outerHTML + ' could not be accessed.'); | |||
anError(STRINGS.ERROR_FILE_ACCESS[config.languageCode] + " " + a.outerHTML); | |||
}; | |||
for (i = 0; i < panoImage.length; i++) { | |||
@@ -362,7 +372,7 @@ function init() { | |||
var a = document.createElement('a'); | |||
a.href = encodeURI(p); | |||
a.innerHTML = a.href; | |||
anError('The file ' + a.outerHTML + ' could not be accessed.'); | |||
anError(STRINGS.ERROR_FILE_ACCESS[config.languageCode] + " " + a.outerHTML); | |||
} | |||
var img = this.response; | |||
parseGPanoXMP(img); | |||
@@ -398,7 +408,7 @@ function init() { | |||
xhr.open('GET', p, true); | |||
} catch (e) { | |||
// Malformed URL | |||
anError('There is something wrong with the panorama URL.'); | |||
anError(STRINGS.ERROR_PANORAMA_URL[config.languageCode]); | |||
} | |||
xhr.responseType = 'blob'; | |||
xhr.setRequestHeader('Accept', 'image/*,*/*;q=0.9'); | |||
@@ -485,9 +495,7 @@ function parseGPanoXMP(image) { | |||
if (navigator.userAgent.toLowerCase().match(/(iphone|ipod|ipad).* os 8_/)) { | |||
var flagIndex = img.indexOf('\xff\xc2'); | |||
if (flagIndex < 0 || flagIndex > 65536) { | |||
anError("Due to iOS 8's broken WebGL implementation, only " + | |||
"progressive encoded JPEGs work for your device (this " + | |||
"panorama uses standard encoding)."); | |||
anError(STRINGS.ERROR_IOS8_WEBGL[config.languageCode]); | |||
} | |||
} | |||
@@ -569,7 +577,7 @@ function parseGPanoXMP(image) { | |||
*/ | |||
function anError(errorMsg) { | |||
if (errorMsg === undefined) | |||
errorMsg = 'Your browser does not have the necessary WebGL support to display this panorama.'; | |||
errorMsg = STRINGS.ERROR_WEBGL[config.languageCode]; | |||
infoDisplay.errorMsg.innerHTML = '<p>' + errorMsg + '</p>'; | |||
controls.load.style.display = 'none'; | |||
infoDisplay.load.box.style.display = 'none'; | |||
@@ -647,6 +655,7 @@ function onDocumentMouseDown(event) { | |||
// Log pitch / yaw of mouse click when debugging / placing hot spots | |||
if (config.hotSpotDebug) { | |||
var coords = mouseEventToCoords(event); | |||
// Dont translate this because the config fields are in english! | |||
console.log('Pitch: ' + coords[0] + ', Yaw: ' + coords[1] + ', Center Pitch: ' + | |||
config.pitch + ', Center Yaw: ' + config.yaw + ', HFOV: ' + config.hfov); | |||
} | |||
@@ -1526,6 +1535,8 @@ function renderInit() { | |||
params.horizonRoll = config.horizonRoll * Math.PI / 180; | |||
if (config.backgroundColor !== undefined) | |||
params.backgroundColor = config.backgroundColor; | |||
if (config.languageCode !== undefined) | |||
params.languageCode = config.languageCode; | |||
renderer.init(panoImage, config.type, config.dynamic, config.haov * Math.PI / 180, config.vaov * Math.PI / 180, config.vOffset * Math.PI / 180, renderInitCallback, params); | |||
if (config.dynamic !== true) { | |||
// Allow image to be garbage collected | |||
@@ -1538,12 +1549,9 @@ function renderInit() { | |||
if (event.type == 'webgl error' || event.type == 'no webgl') { | |||
anError(); | |||
} else if (event.type == 'webgl size error') { | |||
anError('This panorama is too big for your device! It\'s ' + | |||
event.width + 'px wide, but your device only supports images up to ' + | |||
event.maxWidth + 'px wide. Try another device.' + | |||
' (If you\'re the author, try scaling down the image.)'); | |||
anError(STRINGS.ERROR_IMG_TOO_BIG[config.languageCode] + " " + STRINGS.TEXT_IMG_SUPPORTED_WIDTH[config.languageCode] + event.maxWidth + "px.") | |||
} else { | |||
anError('Unknown error. Check developer console.'); | |||
anError(STRINGS.ERROR_UNKNOWN[config.languageCode]); | |||
throw event; | |||
} | |||
} | |||
@@ -1872,12 +1880,12 @@ function processOptions(isPreview) { | |||
break; | |||
case 'author': | |||
infoDisplay.author.innerHTML = 'by ' + escapeHTML(config[key]); | |||
infoDisplay.author.innerHTML = STRINGS.TEXT_BY[config.languageCode] + ' ' + escapeHTML(config[key]); | |||
infoDisplay.container.style.display = 'inline'; | |||
break; | |||
case 'fallback': | |||
infoDisplay.errorMsg.innerHTML = '<p>Your browser does not support WebGL.<br><a href="' + encodeURI(config[key]) + '" target="_blank">Click here to view this panorama in an alternative viewer.</a></p>'; | |||
infoDisplay.errorMsg.innerHTML = '<p>' + STRINGS.ERROR_NO_WEBGL[config.languageCode] + '<br><a href="' + encodeURI(config[key]) + '" target="_blank">' + STRINGS.TEXT_ALTERNATIVE_VIEWER[config.languageCode] + '</a></p>'; | |||
break; | |||
case 'hfov': | |||
@@ -2050,7 +2058,7 @@ function constrainHfov(hfov) { | |||
} | |||
if (minHfov > config.maxHfov) { | |||
// Don't change view if bounds don't make sense | |||
console.log('HFOV bounds do not make sense (minHfov > maxHfov).') | |||
console.log(STRINGS.ERROR_HFOV_BOUNDS[config.languageCode]); | |||
return config.hfov; | |||
} if (hfov < minHfov) { | |||
return minHfov; | |||
@@ -2673,7 +2681,7 @@ this.addHotSpot = function(hs, sceneId) { | |||
} | |||
initialConfig.scenes[id].hotSpots.push(hs); // Add hot spot to config | |||
} else { | |||
throw 'Invalid scene ID!' | |||
throw STRINGS.ERROR_INVALID_SCENE_ID[config.languageCode]; | |||
} | |||
} | |||
if (sceneId === undefined || config.scene == sceneId) { | |||
@@ -0,0 +1,92 @@ | |||
var STRINGS = { | |||
"ERROR_INVALID_IMG_TYPE": { | |||
"de": "Error: Falscher Bild-Typ angegeben!", | |||
"en": "Error: invalid image type specified!", | |||
"ru": "Ошибка: неправильный тип изображения!" | |||
}, | |||
"ERROR_IMG_TOO_BIG": { | |||
"de": "Error: Das Bild ist zu groß. Ihr Gerät unterstützt dies nicht.", | |||
"en": "Error: The image is too big; Your device does not support it.", | |||
"ru": "Ошибка: Разрешение этого изображения слишком велико. Ваш прибор это не поддерживает." | |||
}, | |||
"ERROR_CUBE_FACE_TOO_BIG": { | |||
"de": "Error: Das Cube Face ist zu groß. Ihr gerät unterstützt dies nicht.", | |||
"en": "Error: The cube face is too big; Your device does not support it.", | |||
"ru": "Ошибка: Изображение верхней стороны кубика слишком велико. Ваш прибор это не поддерживает." | |||
}, | |||
"ERROR_NO_WEBGL": { | |||
"de": "Error: WebGL wird nicht unterstützt!", | |||
"en": "Error: no WebGL support detected!", | |||
"ru": "Ошибка: WebGL не поддерживается!" | |||
}, | |||
"ERROR_WEBGL": { | |||
"de": "Error: Ein WebGL Fehler ist aufgetreten!", | |||
"en": "Error: Something went wrong with WebGL!", | |||
"ru": "Ошибка: WebGL Ошибка!" | |||
}, | |||
"ERROR_WEBGL_1286": { | |||
"de": "Error: Verkleinere Canvas wegen Error 1286!", | |||
"en": "Error: Reducing canvas size due to error 1286!", | |||
"ru": "Ошибка: Из за ошибки 1286 уменьшаю изображение на экране." | |||
}, | |||
"ERROR_NO_PANORAMA": { | |||
"de": "Error: Es wurde kein Panorama angegeben.", | |||
"en": "Error: No panorama image was specified.", | |||
"ru": "Ошибка: Панорама не найдена." | |||
}, | |||
"ERROR_FILE_ACCESS": { | |||
"de": "Error: Datei konnte nicht geöffnet werden.", | |||
"en": "Error: File could not be accessed.", | |||
"ru": "Ошибка: Файл не открывается." | |||
}, | |||
"ERROR_PANORAMA_URL": { | |||
"de": "Error: Da stimmt etwas nicht mit der Panorama URL.", | |||
"en": "Error: There is something wrong with the panorama URL.", | |||
"ru": "Ошибка: URL Панорамы повреждён." | |||
}, | |||
"ERROR_IOS8_WEBGL": { | |||
"de": "Error: Wegen der fehlerhaften WebGL Implementierung von iOS8, funktionieren nur progressiv enkodierte JPEGs auf ihrem Gerät (dieses Panorama benutzt standard Enkodierung).", | |||
"en": "Error: Due to iOS 8's broken WebGL implementation, only progressive encoded JPEGs work for your device (this panorama uses standard encoding).", | |||
"ru": "Ошибка: Из за ошибочной WebGL реализации в iOS8, на вашем приборе работают только прогрессивно кодированные JPEGs. (Для этой Панорамы используется стандартная кодировка.)" | |||
}, | |||
"ERROR_UNKNOWN": { | |||
"de": "Error: Ein unbekannter Fehler ist aufgetreten. Schauen Sie in die Entwicklerkonsole.", | |||
"en": "Error: An unknown error occured. Check developer console.", | |||
"ru": "Ошибка: Произошла неизвестная ошибка. Обратитесь в консоль разработчика." | |||
}, | |||
"ERROR_HFOV_BOUNDS": { | |||
"de": "Error: HFOV Grenzen machen keinen Sinn (minHfov > maxHfov).", | |||
"en": "Error: HFOV bounds do not make sense (minHfov > maxHfov).", | |||
"ru": "Ошибка: HFOV границы не имеют смысла(minHfov > maxHfov)." | |||
}, | |||
"ERROR_INVALID_SCENE_ID": { | |||
"de": "Error: Falsche scene ID!", | |||
"en": "Error: Invalid scene ID!", | |||
"ru": "Ошибка: неправильный идентификатор сцены!" | |||
}, | |||
"TEXT_IMG_SUPPORTED_WIDTH": { | |||
"de": "Die maximal unterstützte Breite für ihr Gerät ist: ", | |||
"en": "This device's maximum supported width is: ", | |||
"ru": "Максимально поддерживаемая ширина изображения для вашего прибора: " | |||
}, | |||
"TEXT_IMG_TOO_BIG_ADVICE": { | |||
"de": "(Falls Sie der Author sind, versuchen Sie das Bild herunterzuskalieren.)", | |||
"en": "(If you're the author, try scaling down the image.)", | |||
"ru": "(Если Вы являетесь автором, попробуйте уменьшить разрешение изображения.)" | |||
}, | |||
"TEXT_CLICK_TO_LOAD": { | |||
"de": "Klicke um\nPanorama\nzu laden", | |||
"en": "Click to\nLoad\nPanorama", | |||
"ru": "нажмите здесь чтобы\nзагрузить\nпанораму" | |||
}, | |||
"TEXT_ALTERNATIVE_VIEWER": { | |||
"de": "Klicken Sie hier um das Panorama über einen alternativen Weg anzuschauen.", | |||
"en": "Click here to view this panorama in an alternative viewer.", | |||
"ru": "Нажмите здесь, чтобы увидеть Панораму по другому." | |||
}, | |||
"TEXT_BY": { | |||
"de": "von", | |||
"en": "by", | |||
"ru": "от" | |||
} | |||
} |
@@ -15,6 +15,7 @@ | |||
</div> | |||
</noscript> | |||
</div> | |||
<script type="text/javascript" src="../js/strings.js"></script> | |||
<script type="text/javascript" src="../js/libpannellum.js"></script> | |||
<script type="text/javascript" src="../js/RequestAnimationFrame.js"></script> | |||
<script type="text/javascript" src="../js/pannellum.js"></script> | |||
@@ -37,7 +37,7 @@ function parseURLParameters() { | |||
configFromURL[option] = JSON.parse(value); | |||
break; | |||
case 'author': case 'title': case 'firstScene': case 'fallback': | |||
case 'preview': case 'panorama': case 'config': | |||
case 'preview': case 'panorama': case 'config': case 'languageCode': | |||
configFromURL[option] = decodeURIComponent(value); | |||
break; | |||
default: | |||
@@ -7,6 +7,7 @@ import subprocess | |||
import urllib.parse | |||
JS = [ | |||
'js/strings.js', | |||
'js/libpannellum.js', | |||
'js/RequestAnimationFrame.js', | |||
'js/pannellum.js', | |||