@@ -0,0 +1 @@ | |||||
../../стороннее/base64-js |
@@ -0,0 +1 @@ | |||||
../../стороннее/pako |
@@ -0,0 +1 @@ | |||||
../../стороннее/matter-js |
@@ -0,0 +1 @@ | |||||
../../стороннее/ace |
@@ -0,0 +1 @@ | |||||
../../стороннее/uikit |
@@ -0,0 +1,31 @@ | |||||
function События() | |||||
{ | |||||
this.создать = function() | |||||
{ | |||||
this.обработчики = []; | |||||
}; | |||||
this.подписать = function(обработчик) | |||||
{ | |||||
this.обработчики.push(обработчик); | |||||
}; | |||||
this.отписать = function(обработчик) { | |||||
var номер = this.обработчики.indexOf(обработчик); | |||||
if (номер != -1) | |||||
{ | |||||
this.обработчики.splice(номер, 1); | |||||
} | |||||
}; | |||||
this.уведомить = function(событие) { | |||||
for (var номер in this.обработчики) | |||||
{ | |||||
var обработчик = this.обработчики[номер]; | |||||
обработчик.обработатьСобытие(событие); | |||||
} | |||||
}; | |||||
// Конструктор. | |||||
this.создать(); | |||||
} |
@@ -0,0 +1,61 @@ | |||||
function Состояние() | |||||
{ | |||||
this.создать = function() | |||||
{ | |||||
this.обработчик = null; | |||||
this.значения = {}; | |||||
}; | |||||
this.выпрямить = function(obj) | |||||
{ | |||||
// https://stackoverflow.com/a/42121920 | |||||
var newObj = {}; | |||||
for (var key in obj) | |||||
{ | |||||
if (typeof obj[key] === 'object' && obj[key] !== null) | |||||
{ | |||||
var temp = this.выпрямить(obj[key]) | |||||
for (var key2 in temp) | |||||
{ | |||||
newObj[key + "." + key2] = temp[key2]; | |||||
} | |||||
} | |||||
else | |||||
{ | |||||
newObj[key] = obj[key]; | |||||
} | |||||
} | |||||
return newObj; | |||||
}; | |||||
this.лишьНовыеЗначения = function(значения) { | |||||
var новые = {}; | |||||
for (var ключ in значения) | |||||
{ | |||||
var было = this.значения[ключ]; | |||||
var стало = значения[ключ]; | |||||
if (!(было != null && было == стало)) | |||||
{ | |||||
новые[ключ] = стало; | |||||
this.значения[ключ] = стало; | |||||
} | |||||
} | |||||
return новые; | |||||
}; | |||||
this.разобрать = function(словарь) { | |||||
var значения = this.лишьНовыеЗначения(this.выпрямить(словарь)); | |||||
for (var ключ in значения) | |||||
{ | |||||
var путь = ключ.split("."); | |||||
var значение = значения[ключ]; | |||||
if (this.обработчик) | |||||
{ | |||||
this.обработчик(ключ, путь, значение); | |||||
} | |||||
} | |||||
}; | |||||
// Конструктор. | |||||
this.создать(); | |||||
} |
@@ -0,0 +1,73 @@ | |||||
var мир = {}; | |||||
мир.uuid = function() | |||||
{ | |||||
// https://stackoverflow.com/a/2117523 | |||||
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace( | |||||
/[xy]/g, | |||||
function(c) | |||||
{ | |||||
var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8); | |||||
return v.toString(16); | |||||
} | |||||
); | |||||
}; | |||||
мир.вZ64 = function(строка) | |||||
{ | |||||
var байты = new TextEncoder("utf-8").encode(строка); | |||||
var архив = pako.deflate(байты, { to: 'string' }); | |||||
return base64js.fromByteArray(архив); | |||||
}; | |||||
мир.изZ64 = function(строка) | |||||
{ | |||||
var архив = base64js.toByteArray(строка); | |||||
var байты = pako.inflate(архив); | |||||
return new TextDecoder("utf-8").decode(байты); | |||||
}; | |||||
мир.назначитьКнопкамСобытия = function(события, список) | |||||
{ | |||||
for (var номер in список) | |||||
{ | |||||
const пара = список[номер]; | |||||
var кнопка = document.getElementById(пара[0]); | |||||
кнопка.addEventListener("click", function(_) { | |||||
события.уведомить(пара[1]); | |||||
}); | |||||
} | |||||
}; | |||||
мир.параметрыЗапуска = function() | |||||
{ | |||||
var параметры = {}; | |||||
var запрос = window.location.search.substring(1); | |||||
var аргументы = запрос.split("&"); | |||||
for (var номер in аргументы) | |||||
{ | |||||
var арг = аргументы[номер]; | |||||
var позицияЗнака = арг.indexOf("="); | |||||
// Лишь ключ. | |||||
if (позицияЗнака == -1) | |||||
{ | |||||
var ключ = decodeURIComponent(арг); | |||||
параметры[ключ] = null; | |||||
} | |||||
// Ключ со значением. | |||||
else | |||||
{ | |||||
var сыройКлюч = арг.slice(0, позицияЗнака); | |||||
var сыроеЗначение = арг.slice(позицияЗнака + 1); | |||||
var ключ = decodeURIComponent(сыройКлюч); | |||||
var значение = decodeURIComponent(сыроеЗначение); | |||||
параметры[ключ] = значение; | |||||
} | |||||
} | |||||
return параметры; | |||||
}; | |||||
мир.разобрать = function(словарь) | |||||
{ | |||||
мир.состояние.разобрать(словарь); | |||||
}; |
@@ -0,0 +1 @@ | |||||
../../стороннее/base64-js |
@@ -0,0 +1 @@ | |||||
../../стороннее/pako |
@@ -0,0 +1,10 @@ | |||||
function Заголовок() | |||||
{ | |||||
this.обработатьКлюч = function(ключ, путь, значение) | |||||
{ | |||||
if (ключ == "заголовок") | |||||
{ | |||||
document.title = значение; | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,104 @@ | |||||
function Изображения(корень) | |||||
{ | |||||
this.создать = function() | |||||
{ | |||||
this.умолчание = { | |||||
x: 0, | |||||
y: 0, | |||||
ширина: 100, | |||||
высота: 100, | |||||
угол: 0, | |||||
}; | |||||
this.задано = {}; | |||||
this.элементы = {}; | |||||
}; | |||||
this.создатьИлиПолучитьЭлемент = function(имя) | |||||
{ | |||||
var эл = this.элементы[имя]; | |||||
if (эл) | |||||
{ | |||||
return эл; | |||||
} | |||||
var ум = this.умолчание; | |||||
эл = document.createElement("div"); | |||||
эл.id = `изображения-${имя}`; | |||||
эл.style.position = "absolute"; | |||||
эл.style.display = "block"; | |||||
// Свойства по умолчанию. | |||||
эл.style.left = `${ум.x}px`; | |||||
эл.style.top = `${ум.y}px`; | |||||
эл.style.width = `${ум.ширина}px`; | |||||
эл.style.height = `${ум.высота}px`; | |||||
эл.style.transform = `rotate(${ум.угол}rad)`; | |||||
this.элементы[имя] = эл; | |||||
корень.appendChild(эл); | |||||
return эл; | |||||
}; | |||||
this.обновитьЭлемент = function(имя, свойство, значение) | |||||
{ | |||||
if (!this.задано[имя]) | |||||
{ | |||||
this.задано[имя] = {}; | |||||
} | |||||
this.задано[имя][свойство] = значение; | |||||
var за = this.задано[имя]; | |||||
var эл = this.создатьИлиПолучитьЭлемент(имя); | |||||
if (свойство == "ширина") | |||||
{ | |||||
эл.style.width = `${за.ширина}px`; | |||||
} | |||||
else if (свойство == "высота") | |||||
{ | |||||
эл.style.height = `${за.высота}px`; | |||||
} | |||||
else if ( | |||||
(свойство == "x") || | |||||
(свойство == "y") || | |||||
(свойство == "угол") || | |||||
(свойство == "вид.transform") | |||||
) { | |||||
var ум = this.умолчание; | |||||
var x = за.x ? за.x : ум.x; | |||||
var y = за.y ? за.y : ум.y; | |||||
var угол = за.угол ? за.угол : ум.угол; | |||||
this.обновитьРасположение(имя, x, y, угол); | |||||
} | |||||
else if (свойство.startsWith("вид")) | |||||
{ | |||||
var параметр = свойство.substring(4); | |||||
эл.style.setProperty(параметр, значение); | |||||
} | |||||
}; | |||||
this.обновитьРасположение = function(имя, x, y, угол) | |||||
{ | |||||
var за = this.задано[имя]; | |||||
var эл = this.элементы[имя]; | |||||
эл.style.left = `${x}px`; | |||||
эл.style.top = `${y}px`; | |||||
var transform = `rotate(${угол}deg) `; | |||||
if (за["вид.transform"]) | |||||
{ | |||||
transform += за["вид.transform"]; | |||||
} | |||||
эл.style.transform = transform; | |||||
}; | |||||
this.обработатьКлюч = function(ключ, путь, значение) | |||||
{ | |||||
if (путь[0] == "изображения") | |||||
{ | |||||
var имя = путь[1]; | |||||
var свойство = путь.slice(2).join("."); | |||||
this.обновитьЭлемент(имя, свойство, значение); | |||||
} | |||||
}; | |||||
// Конструктор. | |||||
this.создать(); | |||||
} |
@@ -0,0 +1,19 @@ | |||||
function Физика() | |||||
{ | |||||
this.создать = function() | |||||
{ | |||||
this.движок = Matter.Engine.create({ | |||||
enableSleeping: true, | |||||
gravity: { | |||||
y: 2, | |||||
}, | |||||
}); | |||||
}; | |||||
this.обновить = function() { | |||||
Matter.Engine.update(this.движок); | |||||
}; | |||||
// Конструктор. | |||||
this.создать(); | |||||
} |
@@ -0,0 +1,59 @@ | |||||
function Игра() | |||||
{ | |||||
this.создать = function() | |||||
{ | |||||
var корень = document.getElementById("корень"); | |||||
мир.события = new События(); | |||||
мир.состояние = new Состояние(); | |||||
мир.состояние.обработчик = (к, п, з) => { this.обработатьКлюч(к, п, з) }; | |||||
this.ключники = [ | |||||
new Заголовок(), | |||||
new Изображения(корень), | |||||
]; | |||||
мир.физика = new Физика(); | |||||
var z64 = мир.параметрыЗапуска()["z64"]; | |||||
this.исполнитьКод(z64); | |||||
this.отслеживатьОбновленияКода(); | |||||
this.обновить(); | |||||
}; | |||||
this.исполнитьКод = function(z64) | |||||
{ | |||||
if (z64) | |||||
{ | |||||
var код = мир.изZ64(z64); | |||||
eval(код); | |||||
} | |||||
}; | |||||
this.обновить = function() | |||||
{ | |||||
мир.физика.обновить(); | |||||
var тут = this; | |||||
requestAnimationFrame(function() { | |||||
тут.обновить(); | |||||
}); | |||||
}; | |||||
this.обработатьКлюч = function(ключ, путь, значение) | |||||
{ | |||||
for (var номер in this.ключники) | |||||
{ | |||||
var ключник = this.ключники[номер]; | |||||
ключник.обработатьКлюч(ключ, путь, значение); | |||||
} | |||||
}; | |||||
this.отслеживатьОбновленияКода = function() | |||||
{ | |||||
var тут = this; | |||||
window.addEventListener("message", function(событие) { | |||||
тут.исполнитьКод(событие.data); | |||||
}); | |||||
}; | |||||
// Конструктор. | |||||
this.создать(); | |||||
} |
@@ -0,0 +1,31 @@ | |||||
<!DOCTYPE html> | |||||
<html> | |||||
<head> | |||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/> | |||||
<script src="../общее/base64-js/base64js.min.js"></script> | |||||
<script src="../общее/pako/pako.min.js"></script> | |||||
<script src="matter-js/matter.min.js"></script> | |||||
<style> | |||||
#корень | |||||
{ | |||||
position: relative; | |||||
} | |||||
</style> | |||||
</head> | |||||
<body> | |||||
<div id="заставка"></div> | |||||
<div id="корень"></div> | |||||
<div id="отладка"></div> | |||||
<script src="../общее/100.События.js"></script> | |||||
<script src="../общее/150.Состояние.js"></script> | |||||
<script src="../общее/200.функции.js"></script> | |||||
<script src="050.Заголовок.js"></script> | |||||
<script src="100.Изображения.js"></script> | |||||
<script src="120.Физика.js"></script> | |||||
<script src="700.Игра.js"></script> | |||||
<script> | |||||
var игра = new Игра(); | |||||
</script> | |||||
</body> | |||||
</html> |
@@ -0,0 +1 @@ | |||||
../../стороннее/matter-js |
@@ -0,0 +1,127 @@ | |||||
function Редактор(события, имяРедактора) | |||||
{ | |||||
this.создать = function() | |||||
{ | |||||
this.первоначальныйЗаголовок = document.title; | |||||
this.установитьAce(); | |||||
this.улавливатьЗавершениеРедактирования(); | |||||
this.задатьКодПриЗапуске(); | |||||
события.подписать(this); | |||||
}; | |||||
this.задатьКодПриЗапуске = function() | |||||
{ | |||||
var z64 = мир.параметрыЗапуска()["z64"]; | |||||
if (z64) | |||||
{ | |||||
var код = мир.изZ64(z64); | |||||
this.ace.session.setValue(код); | |||||
} | |||||
}; | |||||
this.запуститьОтдельно = function() | |||||
{ | |||||
var содержимое = this.ace.session.getValue(); | |||||
var z64 = мир.вZ64(содержимое); | |||||
var путь = window.location.pathname + "/../../проигрыватель/index.html?z64=" + z64; | |||||
window.open(путь); | |||||
}; | |||||
this.исполнитьКод = function() | |||||
{ | |||||
var содержимое = this.ace.session.getValue(); | |||||
var z64 = мир.вZ64(содержимое); | |||||
// Исполняем явно код лишь первый раз. | |||||
var проигрыватель = document.getElementById("проигрыватель"); | |||||
if (!проигрыватель.src) | |||||
{ | |||||
проигрыватель.src = "../проигрыватель/index.html?z64=" + z64; | |||||
} | |||||
// После запуска уведомляем iframe о новом коде без перезагрузки. | |||||
else | |||||
{ | |||||
window.frames.проигрыватель.postMessage(z64, "*"); | |||||
} | |||||
}; | |||||
this.обновитьАдреснуюСтрокуИЗаголовок = function() | |||||
{ | |||||
var содержимое = this.ace.session.getValue(); | |||||
var z64 = мир.вZ64(содержимое); | |||||
var путь = window.location.pathname + "?z64=" + z64; | |||||
history.pushState(null, "", путь); | |||||
document.title = `${this.первоначальныйЗаголовок} ${z64.length}:${z64.slice(z64.length - 5)}`; | |||||
}; | |||||
this.обработатьСобытие = function(событие) | |||||
{ | |||||
if (событие == "завершили редактирование") | |||||
{ | |||||
this.обновитьАдреснуюСтрокуИЗаголовок(); | |||||
this.проверитьКорректностьКода(); | |||||
} | |||||
else if (событие == "код корректен") | |||||
{ | |||||
this.исполнитьКод(); | |||||
} | |||||
else if (событие == "перезапустить") | |||||
{ | |||||
this.перезапуститьКод(); | |||||
} | |||||
else if (событие == "запустить отдельно") | |||||
{ | |||||
this.запуститьОтдельно(); | |||||
} | |||||
}; | |||||
this.перезапуститьКод = function() | |||||
{ | |||||
var содержимое = this.ace.session.getValue(); | |||||
var z64 = мир.вZ64(содержимое); | |||||
var проигрыватель = document.getElementById("проигрыватель"); | |||||
проигрыватель.src = "../проигрыватель/index.html?z64=" + z64; | |||||
}; | |||||
this.проверитьКорректностьКода = function() | |||||
{ | |||||
try | |||||
{ | |||||
eval(this.ace.session.getValue()); | |||||
события.уведомить("код корректен"); | |||||
} | |||||
catch (ошибка) | |||||
{ | |||||
события.уведомить("код некорректен"); | |||||
} | |||||
}; | |||||
this.улавливатьЗавершениеРедактирования = function() | |||||
{ | |||||
var тут = this; | |||||
this.ace.session.on("change", function(дельта) { | |||||
const билет = мир.uuid(); | |||||
тут.билет = билет; | |||||
setTimeout( | |||||
function() | |||||
{ | |||||
if (билет == тут.билет) | |||||
{ | |||||
события.уведомить("завершили редактирование"); | |||||
} | |||||
}, | |||||
300 | |||||
); | |||||
}); | |||||
}; | |||||
this.установитьAce = function() | |||||
{ | |||||
var область = document.getElementById(имяРедактора); | |||||
this.ace = window.ace.edit(имяРедактора); | |||||
this.ace.session.setMode("ace/mode/javascript"); | |||||
this.ace.session.setUseWrapMode(true); | |||||
}; | |||||
// Конструктор. | |||||
this.создать(); | |||||
} |
@@ -0,0 +1,48 @@ | |||||
function Пульт(события, имяОшибки, имяПерезапуска, имяОтдельно, имяСсылки) | |||||
{ | |||||
this.создать = function() | |||||
{ | |||||
var ошибка = document.getElementById(имяОшибки); | |||||
мир.назначитьКнопкамСобытия( | |||||
события, | |||||
[ | |||||
[имяПерезапуска, "перезапустить"], | |||||
[имяОтдельно, "запустить отдельно"], | |||||
[имяСсылки, "скопировать ссылку"], | |||||
] | |||||
); | |||||
события.подписать(this); | |||||
}; | |||||
this.обработатьСобытие = function(событие) | |||||
{ | |||||
if (событие == "код корректен") | |||||
{ | |||||
this.показатьОшибку(false); | |||||
} | |||||
else if (событие == "код некорректен") | |||||
{ | |||||
this.показатьОшибку(true); | |||||
} | |||||
else if (событие == "скопировать ссылку") | |||||
{ | |||||
navigator.clipboard.writeText(window.location) | |||||
.then(() => { | |||||
UIkit.notification("Скопировали ссылку", { status: "success", timeout: 1000 }); | |||||
}) | |||||
.catch(err => { | |||||
UIkit.notification("Не удалось скопировать ссылку :(", { status: "danger", timeout: 5000 }); | |||||
}); | |||||
} | |||||
}; | |||||
this.показатьОшибку = function(показать) | |||||
{ | |||||
ошибка.innerHTML = показать ? "<span uk-icon=\"icon: warning\"></span>" : " "; | |||||
}; | |||||
// Конструктор. | |||||
this.создать(); | |||||
} |
@@ -0,0 +1,13 @@ | |||||
function Муром() | |||||
{ | |||||
this.создать = function() | |||||
{ | |||||
мир.события = new События(); | |||||
мир.состояние = new Состояние(); | |||||
мир.редактор = new Редактор(мир.события, "редактор"); | |||||
мир.пульт = new Пульт(мир.события, "ошибка", "перезапустить", "отдельно", "ссылка"); | |||||
}; | |||||
// Конструктор. | |||||
this.создать(); | |||||
} |
@@ -0,0 +1 @@ | |||||
../../стороннее/ace |
@@ -0,0 +1,97 @@ | |||||
<!DOCTYPE html> | |||||
<html> | |||||
<head> | |||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/> | |||||
<title>M-3.2</title> | |||||
<script src="../общее/base64-js/base64js.min.js"></script> | |||||
<script src="../общее/pako/pako.min.js"></script> | |||||
<script src="ace/ace.js"></script> | |||||
<script src="ace/mode-javascript.js"></script> | |||||
<script src="ace/worker-javascript.js"></script> | |||||
<link rel="stylesheet" type="text/css" href="uikit/uikit.min.css" /> | |||||
<script src="uikit/uikit.min.js"></script> | |||||
<script src="uikit/uikit-icons.min.js"></script> | |||||
<style> | |||||
#редактор | |||||
{ | |||||
position: absolute; | |||||
left: 0; | |||||
top: 0; | |||||
bottom: 0; | |||||
width: calc(50% - 2em); | |||||
} | |||||
#пульт | |||||
{ | |||||
position: absolute; | |||||
left: calc(50% - 2em); | |||||
top: 0; | |||||
bottom: 0; | |||||
width: 4em; | |||||
display: flex; | |||||
flex-direction: column; | |||||
border: 1px dashed gainsboro; | |||||
} | |||||
#проигрыватель | |||||
{ | |||||
position: absolute; | |||||
right: 0; | |||||
top: 0; | |||||
bottom: 0; | |||||
width: calc(50% - 2em); | |||||
height: 100%; | |||||
border: 0px none; | |||||
} | |||||
.впульте | |||||
{ | |||||
margin: 0 auto; | |||||
} | |||||
</style> | |||||
</head> | |||||
<body> | |||||
<div id="редактор"></div> | |||||
<div id="пульт"> | |||||
<span id="ошибка" class="впульте"> </span> | |||||
<a id="перезапустить" class="впульте uk-icon-button uk-margin-top" uk-icon="refresh" uk-tooltip="Перезапустить"></a> | |||||
<a id="отдельно" class="впульте uk-icon-button uk-margin-top" uk-icon="desktop" uk-tooltip="Запустить в отдельной вкладке"></a> | |||||
<a id="ссылка" class="впульте uk-icon-button uk-margin-top" uk-icon="link" uk-tooltip="Скопировать ссылку"></a> | |||||
<a id="версия" class="впульте uk-icon-button uk-margin-top" uk-icon="git-branch" uk-tooltip="Версия"></a> | |||||
<div uk-dropdown="mode: click"> | |||||
<table class="uk-table"> | |||||
<tbody> | |||||
<tr> | |||||
<td>3.3</td> | |||||
<td>???</td> | |||||
</tr> | |||||
<tr> | |||||
<td><a href="../../3.2/редактор/index.html">3.2</a></td> | |||||
<td>мир.параметрыЗапуска, мир.разобрать, заголовок, изображения, состояние, физика</td> | |||||
</tr> | |||||
<tr> | |||||
<td><a href="../../3.1/редактор/index.html">3.1</a></td> | |||||
<td>Кнопки перезапуска, копирования ссылки, запуска в отдельной вкладке</td> | |||||
</tr> | |||||
<tr> | |||||
<td><a href="../../3.0/редактор/index.html">3.0</a></td> | |||||
<td>Проверка введённого кода на отсутствие синтаксических ошибок</td> | |||||
</tr> | |||||
</tbody> | |||||
</table> | |||||
</div> | |||||
</div> | |||||
<iframe id="проигрыватель" name="проигрыватель"></iframe> | |||||
<script src="../общее/100.События.js"></script> | |||||
<script src="../общее/150.Состояние.js"></script> | |||||
<script src="../общее/200.функции.js"></script> | |||||
<script src="100.Редактор.js"></script> | |||||
<script src="200.Пульт.js"></script> | |||||
<script src="700.Муром.js"></script> | |||||
<script> | |||||
var муром = new Муром(); | |||||
</script> | |||||
</body> | |||||
</html> |
@@ -0,0 +1 @@ | |||||
../../стороннее/uikit |
@@ -1,4 +1,4 @@ | |||||
<!DOCTYPE html> | <!DOCTYPE html> | ||||
<html> | <html> | ||||
<meta http-equiv="refresh" content="0; URL='3.2/редактор/index.html'"/> | |||||
<meta http-equiv="refresh" content="0; URL='3.3/редактор/index.html'"/> | |||||
</html> | </html> |