アラートダイアログ上にWkWebViewを作成して、さまざまなグラフや3Dアニメーションを表示してきた「箱庭ダイアログ」の1つの到達点、「periodictable」(元素周期表)選択UIの表示デモAppleScriptです。
ローカルに保存しておいた1枚ものの表示用HTML(2020/05に作成したもの)は、当時のSafariで表示できており、WkWebViewでも表示できていました。
当初は、これを「SafariとWkWebViewの差」だと思って、WkWebViewをカスタマイズしまくらないとSafariと同等の表示が行えないのではないか? と考えていました。ただ、探しても探しても答えが見つからず、この方向で情報収集を行っても「無駄」に思えてきました。
WebKit(WkWebView)側のアップデート、およびCDN上のJavaScriptのライブラリのアップデートの相互作用によって表示できなくなったのではないかと、調査の方向を変えてみました。
当時のサンプルHTMLと現在のサンプルHTMLのdiffをとって、読み込むCDN上のライブラリを変更したり、追加することで現行OS上のWkWebView上でも動作するようになりました。
つまり、文字列として与えているHTMLの部分のみ変更しただけで、AppleScript部分とかWkWebViewまわりは一切手をつけていません。わかってしまえば、「なーんだ」という内容ですが、これにはなかなか対応できませんでした。
あとは、クリックした項目のURLイベントをAppleScript側で受信できればなおよいのですが、、、アプリケーションとして独立したものに変更して、AppleScript用語辞書を介して「display periodictable」みたいなコマンドでAppleScript側と値をやりとりするのがよいのかもしれません。
—
– Created by: Takaaki Naganoya
– Created on: 2020年06月13日
– Modified on: 2023年03月07日
—
– Copyright © 2020-2023 Piyomaru Software, All Rights Reserved
—
use AppleScript version "2.7"
use framework "Foundation"
use framework "AppKit"
use framework "WebKit"
use scripting additions
property |NSURL| : a reference to current application’s |NSURL|
property NSAlert : a reference to current application’s NSAlert
property NSString : a reference to current application’s NSString
property NSButton : a reference to current application’s NSButton
property WKWebView : a reference to current application’s WKWebView
property WKUserScript : a reference to current application’s WKUserScript
property NSURLRequest : a reference to current application’s NSURLRequest
property NSRunningApplication : a reference to current application’s NSRunningApplication
property NSUTF8StringEncoding : a reference to current application’s NSUTF8StringEncoding
property WKUserContentController : a reference to current application’s WKUserContentController
property WKWebViewConfiguration : a reference to current application’s WKWebViewConfiguration
property WKUserScriptInjectionTimeAtDocumentEnd : a reference to current application’s WKUserScriptInjectionTimeAtDocumentEnd
property returnCode : 0
–https://www.cresco.co.jp/blog/entry/7427/
— By sgi-chang @ UX Design Center
set myStr to "<!DOCTYPE html>
<html>
<head>
<title>three.js css3d – cresco xmas inspired by periodic table</title>
<meta charset=\"utf-8\">
<meta name=\"viewport\" content=\"width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0\">
<link type=\"text/css\" rel=\"stylesheet\" href=\"https://threejs.org/examples/main.css\">
<style>
a {
color: #8ff;
}
#menu {
position: absolute;
bottom: 20px;
width: 100%;
text-align: center;
}
.element {
width: 120px;
height: 160px;
box-shadow: 0px 0px 12px rgba(0, 255, 255, 0.5);
border: 1px solid rgba(127, 255, 255, 0.25);
font-family: Helvetica, sans-serif;
text-align: center;
line-height: normal;
cursor: default;
}
.element:hover {
box-shadow: 0px 0px 12px rgba(0, 255, 255, 0.75);
border: 1px solid rgba(127, 255, 255, 0.75);
}
.element .number {
position: absolute;
top: 20px;
right: 20px;
font-size: 12px;
color: rgba(127, 255, 255, 0.75);
}
.element .symbol {
position: absolute;
top: 40px;
left: 0px;
right: 0px;
font-size: 60px;
font-weight: bold;
color: rgba(255, 255, 255, 0.75);
text-shadow: 0 0 10px rgba(0, 255, 255, 0.95);
}
.element .details {
position: absolute;
bottom: 15px;
left: 0px;
right: 0px;
font-size: 12px;
color: rgba(127, 255, 255, 0.75);
}
button {
color: rgba(127, 255, 255, 0.75);
background: transparent;
outline: 1px solid rgba(127, 255, 255, 0.75);
border: 0px;
padding: 5px 10px;
cursor: pointer;
}
button:hover {
background-color: rgba(0, 255, 255, 0.5);
}
button:active {
color: #000000;
background-color: rgba(0, 255, 255, 0.75);
}
</style>
</head>
<body>
<script src=\"https://threejs.org/examples/jsm/libs/tween.module.min.js\"></script>
<script src=\"https://threejs.org/examples/jsm/controls/TrackballControls.js\"></script>
<script src=\"https://threejs.org/examples/jsm/renderers/CSS3DRenderer.js\"></script>
<div id=\"info\"><a href=\"http://piyocast.com/as\" target=\"_blank\">AppleScript 3D UI Demonstration</a> By Piyomaru Software</div>
<div id=\"container\"></div>
<div id=\"menu\">
<button id=\"table\">TABLE</button>
<button id=\"sphere\">SPHERE</button>
<button id=\"helix\">HELIX</button>
<button id=\"grid\">GRID</button>
</div>
<!– Import maps polyfill –>
<!– Remove this when import maps will be widely supported –>
<script async src=\"https://unpkg.com/es-module-shims@1.5.8/dist/es-module-shims.js\"></script>
<script type=\"importmap\">
{
\"imports\": {
\"three\": \"https://unpkg.com/three@0.142.0/build/three.module.js\",
\"OrbitControls\": \"https://unpkg.com/three@0.142.0/examples/jsm/controls/OrbitControls.js\"
}
}
</script>
<script type=\"module\">
import * as THREE from ’https://threejs.org/build/three.module.js’;
import { TWEEN } from ’https://threejs.org/examples/jsm/libs/tween.module.min.js’;
import { TrackballControls } from ’https://threejs.org/examples/jsm/controls/TrackballControls.js’;
import { CSS3DRenderer, CSS3DObject } from ’https://threejs.org/examples/jsm/renderers/CSS3DRenderer.js’;
var table = [
\"1\", \"Hydrogen\", \"1.00794\", 1, 1,
\"2\", \"Helium\", \"4.002602\", 1, 2,
\"3\", \"Lithium\", \"6.941\", 1, 3,
\"4\", \"Beryllium\", \"9.012182\", 1, 4,
\"5\", \"Boron\", \"10.811\", 1, 5,
\"6\", \"Carbon\", \"12.0107\", 2, 1,
\"7\", \"Nitrogen\", \"14.0067\", 2, 3,
\"8\", \"Oxygen\", \"15.9994\", 3, 1,
\"9\", \"Fluorine\", \"18.9984032\", 3, 3,
\"101\", \"Saturday\", \"July\", 4, 2,
\"I\", \"Moscovium\", \"(290)\", 6, 1,
\"I\", \"Livermorium\", \"(293)\", 6, 2,
\"I\", \"Tennessine\", \"(294)\", 6, 3,
\"I\", \"Titanium\", \"47.867\", 6, 4,
\"I\", \"Vanadium\", \"50.9415\", 6, 5,
\"Y\", \"Chromium\", \"51.9961\", 8, 1,
\"Y\", \"Manganese\", \"54.938045\", 9, 2,
\"Y\", \"Iron\", \"55.845\", 10, 3,
\"Y\", \"Cobalt\", \"58.933195\", 10, 4,
\"Y\", \"Nickel\", \"58.6934\", 10, 5,
\"Y\", \"Copper\", \"63.546\", 11, 2,
\"Y\", \"Zinc\", \"65.38\", 12, 1,
\"O\", \"Gallium\", \"69.723\", 14, 1,
\"O\", \"Copernicium\", \"(285)\", 14, 2,
\"O\", \"Nihonium\", \"(286)\", 14, 3,
\"O\", \"Oganesson\", \"(294)\", 14, 4,
\"O\", \"Neon\", \"20.1797\", 14, 5,
\"O\", \"Sodium\", \"22.98976…\", 15, 1,
\"O\", \"Magnesium\", \"24.305\", 15, 5,
\"O\", \"Aluminium\", \"26.9815386\", 16, 1,
\"O\", \"Silicon\", \"28.0855\", 16, 5,
\"O\", \"Phosphorus\", \"30.973762\", 17, 1,
\"O\", \"Sulfur\", \"32.065\", 17, 2,
\"O\", \"Chlorine\", \"35.453\", 17, 3,
\"O\", \"Argon\", \"39.948\", 17, 4,
\"O\", \"Potassium\", \"39.948\", 17, 5,
\"M\", \"Calcium\", \"40.078\", 1, 7,
\"M\", \"Scandium\", \"44.955912\", 1, 8,
\"M\", \"Roentgenium\", \"(280)\", 1, 9,
\"M\", \"Germanium\", \"72.63\", 1, 10,
\"M\", \"Lead\", \"207.2\", 1, 11,
\"M\", \"Arsenic\", \"74.9216\", 2, 8,
\"M\", \"Selenium\", \"78.96\", 3, 9,
\"M\", \"Bromine\", \"79.904\", 3, 10,
\"M\", \"Krypton\", \"83.798\", 4, 8,
\"M\", \"Rubidium\", \"85.4678\", 5, 7,
\"M\", \"Strontium\", \"87.62\", 5, 8,
\"M\", \"Yttrium\", \"88.90585\", 5, 9,
\"M\", \"Zirconium\", \"91.224\", 5, 10,
\"M\", \"Niobium\", \"92.90628\", 5, 11,
\"A\", \"Molybdenum\", \"95.96\", 7,8,
\"A\", \"Technetium\", \"(98)\", 7, 9,
\"A\", \"Ruthenium\", \"101.07\", 7, 10,
\"A\", \"Rhodium\", \"102.9055\",7, 11,
\"A\", \"Palladium\", \"106.42\", 8, 7,
\"A\", \"Silver\", \"107.8682\", 8,9,
\"A\", \"Cadmium\", \"112.411\", 9, 7,
\"A\", \"Indium\", \"114.818\", 9, 9,
\"A\", \"Tin\", \"118.71\", 10, 8,
\"A\", \"Antimony\", \"121.76\", 10, 9,
\"A\", \"Gadolinium\", \"157.25\", 10, 10,
\"A\", \"Terbium\", \"158.92535\", 10, 11,
\"R\", \"Dysprosium\", \"162.5\", 12, 7,
\"R\", \"Holmium\", \"164.93032\", 12, 8,
\"R\", \"Erbium\", \"167.259\", 12, 9,
\"R\", \"Thulium\", \"168.93421\", 12, 10,
\"R\", \"Ytterbium\", \"173.054\", 12, 11,
\"R\", \"Lutetium\", \"174.9668\", 13, 7,
\"R\", \"Hafnium\", \"178.49\", 13, 9,
\"R\", \"Samarium\", \"150.36\", 14, 7,
\"R\", \"Europium\", \"151.964\", 14, 9,
\"R\", \"Tantalum\", \"180.94788\", 15, 8,
\"R\", \"Tungsten\", \"183.84\", 15, 10,
\"R\", \"Rhenium\", \"186.207\", 15, 11,
\"U\", \"Osmium\", \"190.23\", 17, 7,
\"U\", \"Iridium\", \"192.217\", 17,8,
\"U\", \"Platinum\", \"195.084\", 17, 9,
\"U\", \"Gold\", \"196.966569\", 17, 10,
\"U\", \"Mercury\", \"200.59\", 18, 11,
\"U\", \"Thallium\", \"204.3833\", 19, 11,
\"U\", \"Bismuth\", \"208.9804\", 20, 7,
\"U\", \"Polonium\", \"(209)\", 20, 8,
\"U\", \"Astatine\", \"(210)\", 20, 9,
\"U\", \"Francium\", \"(223)\", 20, 10,
\"U\", \"Radium\", \"(226)\", 22, 9,
\"U\", \"Actinium\", \"(227)\", 22, 10,
\"U\", \"Thorium\", \"232.03806\", 22, 11,
\"A\", \"Protactinium\", \"231.0588\", 22, 7,
\"A\", \"Uranium\", \"238.02891\", 23, 9,
\"A\", \"Neptunium\", \"(237)\", 23, 8,
\"A\", \"Plutonium\", \"(244)\", 23, 9,
\"A\", \"Americium\", \"(243)\", 23, 10,
\"A\", \"Curium\", \"(247)\", 23, 11,
\"S\", \"Berkelium\", \"(247)\", 24, 7,
\"S\", \"Californium\", \"(251)\", 24, 8,
\"S\", \"Einstenium\", \"(252)\", 24, 9,
\"S\", \"Fermium\", \"(257)\", 24, 11,
\"S\", \"Mendelevium\", \"(258)\", 25, 7,
\"S\", \"Nobelium\", \"(259)\", 25, 9,
\"S\", \"Lawrencium\", \"(262)\", 25, 11,
\"S\", \"Rutherfordium\", \"(267)\", 26, 7,
\"S\", \"Dubnium\", \"(268)\", 26, 9,
\"S\", \"Seaborgium\", \"(271)\", 26, 10,
\"S\", \"Bohrium\", \"(272)\", 26, 11,
\"A\", \"Hassium\", \"(270)\", 27, 8,
\"B\", \"Meitnerium\", \"(276)\", 27, 9,
\"C\", \"Darmstadium\", \"(281)\", 27, 8,
\"D\", \"Tellurium\", \"127.6\", 27, 9,
\"E\", \"Iodine\", \"126.90447\", 27, 10,
\"F\", \"Xenon\", \"131.293\", 28, 9,
\"G\", \"Caesium\", \"132.9054\", 28, 10,
\"H\", \"Barium\", \"132.9054\", 28, 11,
\"I\", \"Lanthanum\", \"138.90547\", 29, 8,
\"J\", \"Cerium\", \"140.116\", 29, 9,
\"K\", \"Praseodymium\", \"140.90765\", 29, 10,
\"L\", \"Neodymium\", \"144.242\", 29, 8,
\"M\", \"Promethium\", \"(145)\", 29, 9,
\"PS\", \"Piyomaru Software\", \"(PiyoPiyo)\", 29, 10,
\"AS\", \"AppleScript\", \"(osalang)\", 29, 11,
];
var camera, scene, renderer;
var controls;
var objects = [];
var targets = { table: [], sphere: [], helix: [], grid: [] };
init();
animate();
function init() {
camera = new THREE.PerspectiveCamera(40, window.innerWidth / window.innerHeight, 1, 10000);
camera.position.z = 3000;
scene = new THREE.Scene();
// table
for (var i = 0; i < table.length; i += 5) {
var element = document.createElement(’div’);
element.className = ’element’;
//element.style.backgroundColor = ’rgba(128,0,64,’ + ( Math.random() * 0.5 + 0.25 ) + ’)’;
//element.style.backgroundColor = ’rgba(64,0,128,’ + ( Math.random() * 0.5 + 0.25 ) + ’)’;
//element.style.backgroundColor = ’rgba(0,0,0,’ + ( Math.random() * 0.5 + 0.25 ) + ’)’;
element.style.backgroundColor = ’rgba(0,127,127,’ + ( Math.random() * 0.5 + 0.25 ) + ’)’;
//element.style.backgroundColor = ’rgba(18,77,174,’ + (Math.random() * 0.5 + 0.25) + ’)’;
var number = document.createElement(’div’);
number.className = ’number’;
number.textContent = (i / 5) + 1;
element.appendChild(number);
var symbol = document.createElement(’div’);
symbol.className = ’symbol’;
symbol.textContent = table[i];
element.appendChild(symbol);
var details = document.createElement(’div’);
details.className = ’details’;
details.innerHTML = table[i + 1] + ’<br>’ + table[i + 2];
element.appendChild(details);
var object = new CSS3DObject(element);
object.position.x = Math.random() * 4000 – 2000;
object.position.y = Math.random() * 4000 – 2000;
object.position.z = Math.random() * 4000 – 2000;
scene.add(object);
objects.push(object);
//
var object = new THREE.Object3D();
object.position.x = (table[i + 3] * 140) – 1330;
object.position.y = – (table[i + 4] * 180) + 990;
targets.table.push(object);
}
// sphere
var vector = new THREE.Vector3();
for (var i = 0, l = objects.length; i < l; i++) {
var phi = Math.acos(- 1 + (2 * i) / l);
var theta = Math.sqrt(l * Math.PI) * phi;
var object = new THREE.Object3D();
object.position.setFromSphericalCoords(800, phi, theta);
vector.copy(object.position).multiplyScalar(2);
object.lookAt(vector);
targets.sphere.push(object);
}
// helix
var vector = new THREE.Vector3();
for (var i = 0, l = objects.length; i < l; i++) {
var theta = i * 0.175 + Math.PI;
var y = – (i * 8) + 450;
var object = new THREE.Object3D();
object.position.setFromCylindricalCoords(900, theta, y);
vector.x = object.position.x * 2;
vector.y = object.position.y;
vector.z = object.position.z * 2;
object.lookAt(vector);
targets.helix.push(object);
}
// grid
for (var i = 0; i < objects.length; i++) {
var object = new THREE.Object3D();
object.position.x = ((i % 5) * 400) – 800;
object.position.y = (- (Math.floor(i / 5) % 5) * 400) + 800;
object.position.z = (Math.floor(i / 25)) * 1000 – 2000;
targets.grid.push(object);
}
//
renderer = new CSS3DRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.getElementById(’container’).appendChild(renderer.domElement);
//
controls = new TrackballControls(camera, renderer.domElement);
controls.minDistance = 500;
controls.maxDistance = 6000;
controls.addEventListener(’change’, render);
var button = document.getElementById(’table’);
button.addEventListener(’click’, function () {
transform(targets.table, 2000);
}, false);
var button = document.getElementById(’sphere’);
button.addEventListener(’click’, function () {
transform(targets.sphere, 2000);
}, false);
var button = document.getElementById(’helix’);
button.addEventListener(’click’, function () {
transform(targets.helix, 2000);
}, false);
var button = document.getElementById(’grid’);
button.addEventListener(’click’, function () {
transform(targets.grid, 2000);
}, false);
transform(targets.table, 2000);
//
window.addEventListener(’resize’, onWindowResize, false);
}
function transform(targets, duration) {
TWEEN.removeAll();
for (var i = 0; i < objects.length; i++) {
var object = objects[i];
var target = targets[i];
new TWEEN.Tween(object.position)
.to({ x: target.position.x, y: target.position.y, z: target.position.z }, Math.random() * duration + duration)
.easing(TWEEN.Easing.Exponential.InOut)
.start();
new TWEEN.Tween(object.rotation)
.to({ x: target.rotation.x, y: target.rotation.y, z: target.rotation.z }, Math.random() * duration + duration)
.easing(TWEEN.Easing.Exponential.InOut)
.start();
}
new TWEEN.Tween(this)
.to({}, duration * 2)
.onUpdate(render)
.start();
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
render();
}
function animate() {
requestAnimationFrame(animate);
TWEEN.update();
controls.update();
}
function render() {
renderer.render(scene, camera);
}
</script>
</body>
</html>"
set paramObj to {myMessage:"WebGL & three.js Test", mySubMessage:"This is a WebGL UI using three.js", htmlStr:myStr}
–my browseStrWebContents:paramObj–for debug
my performSelectorOnMainThread:"browseStrWebContents:" withObject:(paramObj) waitUntilDone:true
on browseStrWebContents:paramObj
set aMainMes to myMessage of paramObj
set aSubMes to mySubMessage of paramObj
set htmlString to (htmlStr of paramObj)
set aWidth to 1600
set aHeight to 900
–WebViewをつくる
set aConf to WKWebViewConfiguration’s alloc()’s init()
–指定HTML内のJavaScriptをFetch
set jsSource to pickUpFromToStr(htmlString, "<script src", "</script>") of me
set userScript to WKUserScript’s alloc()’s initWithSource:jsSource injectionTime:(WKUserScriptInjectionTimeAtDocumentEnd) forMainFrameOnly:true
set userContentController to WKUserContentController’s alloc()’s init()
userContentController’s addUserScript:(userScript)
aConf’s setUserContentController:userContentController
set aWebView to WKWebView’s alloc()’s initWithFrame:(current application’s NSMakeRect(0, 0, aWidth, aHeight)) configuration:aConf
aWebView’s setNavigationDelegate:me
aWebView’s setUIDelegate:me
aWebView’s setTranslatesAutoresizingMaskIntoConstraints:true
using terms from scripting additions
set bURL to |NSURL|’s fileURLWithPath:(POSIX path of (path to me))
end using terms from
aWebView’s loadHTMLString:htmlString baseURL:(bURL)
— set up alert
set theAlert to NSAlert’s alloc()’s init()
tell theAlert
its setMessageText:aMainMes
its setInformativeText:aSubMes
its addButtonWithTitle:"OK"
–its addButtonWithTitle:"Cancel"
its setAccessoryView:aWebView
set myWindow to its |window|
end tell
— show alert in modal loop
NSRunningApplication’s currentApplication()’s activateWithOptions:0
my performSelectorOnMainThread:"doModal:" withObject:(theAlert) waitUntilDone:true
–Stop Web View Action
set bURL to |NSURL|’s URLWithString:"about:blank"
set bReq to NSURLRequest’s requestWithURL:bURL
aWebView’s loadRequest:bReq
if (my returnCode as number) = 1001 then error number -128
end browseStrWebContents:
on doModal:aParam
set (my returnCode) to (aParam’s runModal()) as number
end doModal:
on viewDidLoad:aNotification
return true
end viewDidLoad:
on fetchJSSourceString(aURL)
set jsURL to |NSURL|’s URLWithString:aURL
set jsSourceString to NSString’s stringWithContentsOfURL:jsURL encoding:(NSUTF8StringEncoding) |error|:(missing value)
return jsSourceString
end fetchJSSourceString
on pickUpFromToStr(aStr as string, s1Str as string, s2Str as string)
set a1Offset to offset of s1Str in aStr
if a1Offset = 0 then return false
set bStr to text (a1Offset + (length of s1Str)) thru -1 of aStr
set a2Offset to offset of s2Str in bStr
if a2Offset = 0 then return false
set cStr to text 1 thru (a2Offset – (length of s2Str)) of bStr
return cStr as string
end pickUpFromToStr
–リストを任意のデリミタ付きでテキストに
on retArrowText(aList, aDelim)
set aText to ""
set curDelim to AppleScript’s text item delimiters
set AppleScript’s text item delimiters to aDelim
set aText to aList as text
set AppleScript’s text item delimiters to curDelim
return aText
end retArrowText
on array2DToJSONArray(aList)
set anArray to current application’s NSMutableArray’s arrayWithArray:aList
set jsonData to current application’s NSJSONSerialization’s dataWithJSONObject:anArray options:(0 as integer) |error|:(missing value) –0 is
set resString to current application’s NSString’s alloc()’s initWithData:jsonData encoding:(current application’s NSUTF8StringEncoding)
return resString
end array2DToJSONArray
on parseByDelim(aData, aDelim)
set curDelim to AppleScript’s text item delimiters
set AppleScript’s text item delimiters to aDelim
set dList to text items of aData
set AppleScript’s text item delimiters to curDelim
return dList
end parseByDelim