newhelper-js 2.1.1 → 2.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README-en.md +1 -1
- package/README.md +2 -2
- package/newHelper.js +454 -187
- package/package.json +1 -1
package/README-en.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# newHelper.js
|
|
2
2
|
- [русская версия](README.md)
|
|
3
3
|
- A library for creating ultra-lightweight yet highly functional admin panels, one of the best examples of Object Hub. The total bundle of the entire site is only 280 kb of pure code, and the internal admin panel with all HTML+CSS+JS weighs only 25 kilobytes (with gzip, the size is even smaller). I try to keep the library in a "Modular Monolith" state.
|
|
4
|
-
Current version - 2.1,
|
|
4
|
+
Current version - 2.1, with good documentation in the code
|
|
5
5
|
## Included modules:
|
|
6
6
|
- Advanced window engine (resize, fullscreen, taskbar, 6.29 KB)
|
|
7
7
|
- Convenient hotkeys (press/release callbacks, 1.16 KB)
|
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
- [english version](README-en.md)
|
|
3
3
|
- [документация](docs.md)
|
|
4
4
|
- Библиотека для создания сверхлёгких но очень функциональных админ панелей, один из лучших примеров Object hub, Общий бандл всего сайта составляет всего 280 кб чистого кода, внутренная админ панель со всем html+css+js весит всего 25 килобайт (при gzip размер ещё ниже). Я стараюсь придерживать библиотеку в состоянии "Модульного монолита".
|
|
5
|
-
Текущая версия - 2.1,
|
|
5
|
+
Текущая версия - 2.1.2, с хорошей документацией в коде
|
|
6
6
|
## Модули в комплекте
|
|
7
7
|
- Широкий движок окон (ресайз, разворот на весь экран, taskbar, 6.29 кб)
|
|
8
8
|
- Удобные горячие клавиши (press/release колбеки, 1.16 кб)
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
|
|
17
17
|
## Особенности
|
|
18
18
|
- Библиотека использует классическое подключение с тегом script и не требует никаких сборщиков/компиляторов. Но если очень хочется вы можете настроить его, но зачем? это же ассемблер из мира веб
|
|
19
|
-
- Зависимости? неа, это ванильный
|
|
19
|
+
- Зависимости? неа, это ванильный ES8
|
|
20
20
|
- Указанный в начале размер библиотеки является размером исходного кода, так что минификация+gzip уменьшат размер ещё сильнее
|
|
21
21
|
- Из-за минималистичности кода вы можете спокойно переопределить любой встроенный метод под свои нужны. В крайнем случае можно прибегнуть к модификации кода библиотеки, но такое мы не рекомендуем делать в случае если вы собираетесь обновляться до самых последних версий
|
|
22
22
|
- Весь код написан внутри одного "_" и использованием всего трёх глобальных читателей событий. Из-за этого конфликты с остальными библиотеками минимальны, если вы готовы то можете пробовать запустить newHelper вместе с jQuery или если вы очень хотите реактивность то пробуйте в связке с ультралёгкими реактивными фреймворками (у меня на слуху из адекватных alpine.js)
|
package/newHelper.js
CHANGED
|
@@ -1,15 +1,48 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
/*
|
|
2
|
+
* Стиль комментариев
|
|
3
|
+
* FIXME - странное поведение функции, которое желательно бы переделать
|
|
4
|
+
* ??? - требует уточнения
|
|
5
|
+
* !!! - обратите внимание
|
|
6
|
+
*
|
|
7
|
+
* Перед вами код newHelper.js версии 2.1.2, он построен на базе гибкой псевдофабрики
|
|
8
|
+
* Которая начинается с _=function(){...}();
|
|
9
|
+
* Если у вас есть конфликты с Lodash вы можете переименовать _ на всё что вам нужно
|
|
10
|
+
*
|
|
11
|
+
* НЕСТАБИЛЬНОЕ API (НЕ РЕКОМЕНДУЕТСЯ ДЛЯ PRODUCTION):
|
|
12
|
+
* модуль _.autoForm
|
|
13
|
+
* _.win.read()
|
|
14
|
+
* _.win.write()
|
|
15
|
+
*
|
|
16
|
+
* УДАЛЕНО (вернётся позже):
|
|
17
|
+
* модуль _.err
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
_=function(){let _={
|
|
22
|
+
link:{
|
|
23
|
+
/*
|
|
24
|
+
* МОДУЛЬ ССЫЛОК
|
|
25
|
+
*
|
|
26
|
+
* Работает по принципу [ссылка, команды...]
|
|
27
|
+
* Пример: ?page=home&debug&lang=ru
|
|
28
|
+
* ^^^^^^^^ ^^^^^ ^^^^^^
|
|
29
|
+
* страница команды
|
|
30
|
+
*
|
|
31
|
+
* В процессе разработки ядра 2.0 в Object hub я понял
|
|
32
|
+
* Что команды могут быть очень полезными для отладки
|
|
33
|
+
* Но в теории на них можно повешать все модальные и прочие действия
|
|
34
|
+
*
|
|
35
|
+
* !!!: в функции get() работает весь роутинг, в т.ч. вложенный для страниц
|
|
36
|
+
*/
|
|
4
37
|
basePage:()=>{},
|
|
5
38
|
defTitle:'',
|
|
6
39
|
actions:{},
|
|
7
40
|
commands:{},
|
|
8
|
-
_cmd:[],
|
|
9
41
|
|
|
10
|
-
_i: true,
|
|
42
|
+
_i: true, // _i - блокировщик pushState в set()
|
|
43
|
+
_cmd:[],
|
|
11
44
|
compile:()=>location.search.replace('?','').split('&'),
|
|
12
|
-
set(page,title=
|
|
45
|
+
set(page,title=this.defTitle){
|
|
13
46
|
if (title) _.$.D.title=title;
|
|
14
47
|
if (!this._i){
|
|
15
48
|
let link=this.compile();
|
|
@@ -36,47 +69,78 @@ _.link={
|
|
|
36
69
|
}
|
|
37
70
|
},
|
|
38
71
|
get(){
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
72
|
+
/*
|
|
73
|
+
* Страницы бросают ошибку чтобы вызвать базовую страницу
|
|
74
|
+
* Команды тем временем так не делают
|
|
75
|
+
* Потому что сломанная команда не так страшна как сломанная страница
|
|
76
|
+
*
|
|
77
|
+
* При popstate команды берутся из хранилища _cmd, вместо самой ссылки
|
|
78
|
+
* Сделано это для переноса команд при прыжках по истории
|
|
79
|
+
*/
|
|
80
|
+
let links=this.compile(),
|
|
81
|
+
[firstKey,fisrtValue]=links[0].split('='),
|
|
42
82
|
cmds=links.slice(1);
|
|
43
83
|
try {
|
|
44
|
-
let dirs=
|
|
45
|
-
dir=
|
|
46
|
-
main=dir[
|
|
47
|
-
if (!
|
|
48
|
-
main(
|
|
84
|
+
let dirs=firstKey.split('/'),
|
|
85
|
+
dir=this.actions,
|
|
86
|
+
main=dir[firstKey];
|
|
87
|
+
if (!firstKey.includes('/')){
|
|
88
|
+
main(fisrtValue);
|
|
49
89
|
} else {
|
|
50
90
|
for (let p of dirs){
|
|
51
91
|
let kDir=dir[p+'/'];
|
|
52
92
|
if (kDir)
|
|
53
93
|
dir=kDir;
|
|
54
94
|
else{
|
|
55
|
-
dir[p](
|
|
95
|
+
dir[p](fisrtValue);
|
|
56
96
|
break;
|
|
57
97
|
}
|
|
58
98
|
}
|
|
59
99
|
}
|
|
60
100
|
}catch(e){
|
|
61
|
-
|
|
101
|
+
this.basePage();
|
|
62
102
|
throw e;
|
|
63
103
|
}
|
|
64
|
-
|
|
65
|
-
cmds.forEach(
|
|
66
|
-
let [key,
|
|
67
|
-
let cmd=
|
|
68
|
-
if (cmd)
|
|
69
|
-
|
|
104
|
+
this._cmd=cmds;
|
|
105
|
+
cmds.forEach(cmmdPre=>{
|
|
106
|
+
let [key,value]=cmmdPre.split('=');
|
|
107
|
+
let cmd=this.commands[key];
|
|
108
|
+
if (cmd) cmd(value);
|
|
109
|
+
else console.error(new Error(`command '${cmd}' doesn't exist!`))
|
|
70
110
|
});
|
|
71
111
|
},
|
|
72
|
-
}
|
|
73
|
-
|
|
112
|
+
},
|
|
113
|
+
lazy:{
|
|
114
|
+
/*
|
|
115
|
+
* МОДУЛЬ ЛЕНИ
|
|
116
|
+
*
|
|
117
|
+
* Создаёт в глобальной области видимости прокси функции
|
|
118
|
+
* Вызывающие загрузку скрипта с внещним модулем
|
|
119
|
+
* Был сделан через глобальную область, так намного проще создавать лень
|
|
120
|
+
*
|
|
121
|
+
* !!!: Функции обёртки в register() должны быть повешаны на window
|
|
122
|
+
* Иначе lazy._ провалится в рекурсию ошибок, не наступайте на мои грабли
|
|
123
|
+
*
|
|
124
|
+
* ???: будет ли легче создавать лень в легаси проектах через es6 импорты
|
|
125
|
+
*/
|
|
74
126
|
loaded:{},
|
|
75
127
|
load(url,...args){
|
|
76
|
-
let key=url.split('?')[0],
|
|
128
|
+
let key=url.split('?')[0], // отсекаем параметры, чтобы не дублировать
|
|
77
129
|
state=this.loaded,
|
|
78
130
|
c=state[key];
|
|
79
131
|
|
|
132
|
+
/*
|
|
133
|
+
* ...args передаются в Promise.resolve(args)
|
|
134
|
+
* Это позволяет делать _.lazy.load('script.js', 'данные', 'для', 'колбека')
|
|
135
|
+
* И потом в .then((a,b,c)=>...) получать эти аргументы
|
|
136
|
+
*
|
|
137
|
+
* Тройное состояние скрипта в lazy.loaded:
|
|
138
|
+
* - true: уже загружен => сразу резолвим
|
|
139
|
+
* - Promise: грузится сейчас => ждём тот же промис
|
|
140
|
+
* - undefined: ещё не грузили => создаём новый промис
|
|
141
|
+
*
|
|
142
|
+
* Это защита от двойной загрузки одного скрипта
|
|
143
|
+
*/
|
|
80
144
|
if (c=== true) return Promise.resolve(args);
|
|
81
145
|
if (c instanceof Promise) return c.then(()=>args);
|
|
82
146
|
|
|
@@ -100,42 +164,62 @@ _.lazy={
|
|
|
100
164
|
if (!Array.isArray(funcs)) return new Error('Array required for register');
|
|
101
165
|
|
|
102
166
|
for (let fn of funcs)
|
|
103
|
-
window[fn]=(...a)=>
|
|
167
|
+
window[fn]=(...a)=>this._(scr,fn).then(f=>f(...a));
|
|
168
|
+
// ???: добавить вложенность с созданием объектов-обёрток
|
|
104
169
|
|
|
105
|
-
console.info('
|
|
170
|
+
console.info('lazy> Applied lazy '+scr+' with this functions:',funcs);
|
|
171
|
+
// ???: не мешает ли тут console.info
|
|
106
172
|
},
|
|
107
173
|
async _(scr,fn){
|
|
108
174
|
let w=window,
|
|
109
|
-
|
|
110
|
-
try{await
|
|
175
|
+
wrapper=w[fn];
|
|
176
|
+
try{await this.load(scr)}
|
|
111
177
|
catch(e){throw e}
|
|
112
|
-
if (
|
|
178
|
+
if (wrapper!== w[fn]) return w[fn];
|
|
113
179
|
throw new Error(`Function ${fn} not loaded from ${scr}`);
|
|
114
180
|
},
|
|
115
|
-
}
|
|
116
|
-
|
|
181
|
+
},
|
|
182
|
+
lang:{
|
|
183
|
+
/*
|
|
184
|
+
* МОДУЛЬ ПЕРЕВОДОВ
|
|
185
|
+
*
|
|
186
|
+
* По слухам этот модуль лучше чем многие i18n реализации
|
|
187
|
+
* Всё потому что он из коробки умеет переводить страницу без перезагрузки
|
|
188
|
+
*
|
|
189
|
+
* !!!: parse() обрабатывает ключи из vars и подставляет их значения
|
|
190
|
+
* ваш +ключ+ становится значением, и это значение динамичное
|
|
191
|
+
* Так удобнее отображать динамичные данные на сайтах
|
|
192
|
+
* Например никнейм пользователя
|
|
193
|
+
*/
|
|
117
194
|
addr:'',
|
|
118
195
|
vars:{},
|
|
119
196
|
main:{},
|
|
120
197
|
|
|
121
|
-
_:(i)=>` data-trans="${i}"`,
|
|
122
198
|
load(name){
|
|
123
199
|
return new Promise((resolve,reject)=>{
|
|
124
|
-
_.http.req('GET',
|
|
200
|
+
_.http.req('GET',this.addr+name+'.json',false,{'Cache-Control':'no-cache,no-store,max-age=0'})
|
|
125
201
|
.then(data=>resolve(data));
|
|
126
202
|
})
|
|
203
|
+
// ???: убрать ли захардкоженое отключение кеша
|
|
127
204
|
},
|
|
128
205
|
parse:(packet,vars=_.lang.vars)=>
|
|
206
|
+
/*
|
|
207
|
+
* Регулярка ищет всё, что внутри плюсов, в JSON они редки
|
|
208
|
+
* Это позволяет без конфликтов подставлять переменные если они нашлись
|
|
209
|
+
*
|
|
210
|
+
* ???: переделать под общий синтаксис типа {var}
|
|
211
|
+
*/
|
|
129
212
|
packet.replace(/\+([^+]+)\+/g,(match,key)=>{
|
|
130
213
|
let v=vars[key];
|
|
131
214
|
return v!== undefined ? v : match;
|
|
132
215
|
}),
|
|
133
216
|
async replace(name){
|
|
134
|
-
const p=await this.load(name);
|
|
135
|
-
|
|
217
|
+
const p=await this.load(name); // await короче Promise.then
|
|
218
|
+
this.main=JSON.parse(this.parse(p)); // без замены языка нельзя начинать перевод
|
|
136
219
|
for (let e of _.$.qa('[data-trans]')){
|
|
137
220
|
let key=e.dataset.trans,
|
|
138
|
-
text=
|
|
221
|
+
text=this.main[key] || `<code>lang.get('${key}')</code>`,
|
|
222
|
+
// ???: убрать обёртывание отсутсвующих ключей в <code />
|
|
139
223
|
tag=e.tagName;
|
|
140
224
|
|
|
141
225
|
if (tag=== 'IMG') e.src=text;
|
|
@@ -143,26 +227,57 @@ _.lang={
|
|
|
143
227
|
e[e.type=== 'submit' ? 'value' :'placeholder']=text;
|
|
144
228
|
else e.innerHTML=text;
|
|
145
229
|
}
|
|
230
|
+
// возвращаем для последующей обработки пакета, например для сохранения в _.storage
|
|
146
231
|
return p;
|
|
147
232
|
},
|
|
233
|
+
|
|
234
|
+
/*
|
|
235
|
+
* Получатели строки из пакета автоматически формируют HTML
|
|
236
|
+
* Это позволяет заметно упростить работу с кодом
|
|
237
|
+
* Вместо отдельного указания data-trans и lang.from
|
|
238
|
+
* вы можете написать `<h1${_.lang.text('yourKey')}/h1>`
|
|
239
|
+
* А пришлось бы писать `<h1 data-trans="yourKey">${_.lang.from('yourKey')}</h1>`
|
|
240
|
+
* Согласитесь, и короче и удобнее ведь?
|
|
241
|
+
* Не повторяйте моих ошибок и примите это как победу в лотерее
|
|
242
|
+
*
|
|
243
|
+
* !!!: если ключа в пакете нету, будет выброшен warning
|
|
244
|
+
*/
|
|
245
|
+
attr:(i)=>` data-trans="${i}"`,
|
|
148
246
|
from:i=>_.lang.main[i] || console.warn(`_.lang> ${i} is undefined`) || i,
|
|
149
247
|
|
|
150
|
-
text: i=>_.lang.
|
|
151
|
-
submit: i=>_.lang.
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
248
|
+
text: i=>_.lang.attr(i)+`>${_.lang.from(i)}<`,
|
|
249
|
+
submit: i=>_.lang.attr(i)+`value="${_.lang.from(i)}">`,
|
|
250
|
+
/*
|
|
251
|
+
* <input type=submit> работает во всех браузерах стабильно
|
|
252
|
+
* не используйте на них обычный lang.input()
|
|
253
|
+
* иначе у вас не отобразится текст
|
|
254
|
+
*/
|
|
255
|
+
input: i=>_.lang.attr(i)+`placeholder="${_.lang.from(i)}">`,
|
|
256
|
+
textarea: i=>_.lang.attr(i)+`placeholder="${_.lang.from(i)}"><`,
|
|
257
|
+
img: i=>_.lang.attr(i)+`src="${_.lang.from(i)}"`,
|
|
155
258
|
win(i){
|
|
156
|
-
let text=
|
|
157
|
-
dT=
|
|
259
|
+
let text=this.from(i),
|
|
260
|
+
dT=this.attr(i);
|
|
158
261
|
if (text== null || text== ''){
|
|
159
262
|
text=i;
|
|
160
263
|
dT='';
|
|
161
264
|
}
|
|
162
265
|
return `${dT}>${text}<`;
|
|
163
266
|
},
|
|
164
|
-
}
|
|
165
|
-
|
|
267
|
+
},
|
|
268
|
+
http:{
|
|
269
|
+
/*
|
|
270
|
+
* HTTP-КЛИЕНТ
|
|
271
|
+
*
|
|
272
|
+
* Обычная обёртка нав XHR для быстрых запросов
|
|
273
|
+
* Использую XHR вместо fetch
|
|
274
|
+
* Мне нужен прогресс загрузки (fetch его не даёт)
|
|
275
|
+
* Да и вам тоже не помешает прогресс загрузки
|
|
276
|
+
*
|
|
277
|
+
* В defaultHeaders вы можете установить хедеры по умолчанию
|
|
278
|
+
* Как пример Authorization: 'your token'
|
|
279
|
+
* ???: добавить возможность игнорировать дефолтные хедеры
|
|
280
|
+
*/
|
|
166
281
|
defaultHeaders:{},
|
|
167
282
|
req(method,url,data='',headers={},fileProgressElement=false){
|
|
168
283
|
return new Promise((resolve,reject)=>{
|
|
@@ -170,10 +285,12 @@ _.http={
|
|
|
170
285
|
|
|
171
286
|
xhr.open(method,url);
|
|
172
287
|
|
|
173
|
-
let allHeaders={...
|
|
288
|
+
let allHeaders={...this.defaultHeaders,...headers};
|
|
174
289
|
for (let header in allHeaders)
|
|
175
290
|
xhr.setRequestHeader(header,allHeaders[header]);
|
|
176
291
|
|
|
292
|
+
// !!!: fileProgressElement ожидает <progress> элемент без min/max
|
|
293
|
+
// Потому что value от 0 до 1
|
|
177
294
|
if (fileProgressElement)
|
|
178
295
|
xhr.upload.onprogress=(e)=>{
|
|
179
296
|
if (e.lengthComputable){
|
|
@@ -192,22 +309,36 @@ _.http={
|
|
|
192
309
|
xhr.send(data);
|
|
193
310
|
});
|
|
194
311
|
},
|
|
195
|
-
}
|
|
196
|
-
|
|
312
|
+
},
|
|
313
|
+
$:{
|
|
197
314
|
D: document,
|
|
198
|
-
|
|
199
315
|
id:(i)=>_.$.D.getElementById(i),
|
|
200
316
|
q:(i,p=_.$.D)=>p.querySelector(i),
|
|
201
317
|
qa:(i,p=_.$.D)=>p.querySelectorAll(i),
|
|
202
318
|
|
|
203
319
|
on:(el,ev,fn,opts)=>el.addEventListener(ev,fn,opts),
|
|
204
320
|
off:(el,ev,fn,opts)=>el.removeEventListener(ev,fn,opts),
|
|
205
|
-
|
|
206
|
-
|
|
321
|
+
|
|
322
|
+
cliRect:e=>e.getBoundingClientRect(), // сокращение чтобы не писать 25+ символов
|
|
323
|
+
},
|
|
324
|
+
html(strs,...args){
|
|
325
|
+
/*
|
|
326
|
+
* Шаблонные строки в DOM
|
|
327
|
+
*
|
|
328
|
+
* Позволяет писать _.html`<div>${content}</div>`
|
|
329
|
+
* И получать настоящий DOM-элемент, а не строку
|
|
330
|
+
*
|
|
331
|
+
* Почему через template?
|
|
332
|
+
* - Скрипты не выполняются (никаких xss!)
|
|
333
|
+
* - Можно создать несколько элементов разом
|
|
334
|
+
* - Быстрее чем createElement для сложных структур
|
|
335
|
+
* - Банально удобнее createElement для сложных древ
|
|
336
|
+
*/
|
|
207
337
|
let strF=[];
|
|
208
338
|
for (let i=0; i < args.length; i++)
|
|
209
339
|
strF.push(strs[i],args[i]);
|
|
210
340
|
strF.push(strs[strs.length - 1]);
|
|
341
|
+
// убираем лишние пробелы, чтобы не плодить пустые текстовые ноды
|
|
211
342
|
strF=strF.join('').trim().replace(/\s+/g,' ');
|
|
212
343
|
|
|
213
344
|
const template=_.$.D.createElement('template');
|
|
@@ -217,8 +348,56 @@ _.html=(strs,...args)=>{
|
|
|
217
348
|
if (content.children.length=== 1)
|
|
218
349
|
return content.firstChild;
|
|
219
350
|
return content;
|
|
220
|
-
}
|
|
221
|
-
|
|
351
|
+
},
|
|
352
|
+
autoForm:{
|
|
353
|
+
/*
|
|
354
|
+
* АВТОСОХРАНЕНИЕ ФОРМ
|
|
355
|
+
*
|
|
356
|
+
* Позволяет сохранять состояние формы на случай
|
|
357
|
+
* Если в офисе внезапно выключат свет
|
|
358
|
+
*
|
|
359
|
+
* Реализовывать удаление читателя событий я не стал
|
|
360
|
+
* Зачем удалять обработчик если он вешается на форму а не на Document?
|
|
361
|
+
*/
|
|
362
|
+
autoSave(form,delay=1000,cb){
|
|
363
|
+
let t,
|
|
364
|
+
save=()=>{
|
|
365
|
+
let data={};
|
|
366
|
+
new FormData(form).forEach((v,k)=>{
|
|
367
|
+
if (data[k]!== undefined) {
|
|
368
|
+
if (!Array.isArray(data[k])) data[k]=[data[k]];
|
|
369
|
+
data[k].push(v);
|
|
370
|
+
} else data[k]=v;
|
|
371
|
+
});
|
|
372
|
+
cb(data);
|
|
373
|
+
};
|
|
374
|
+
if(delay<1)return save();
|
|
375
|
+
_.$.on(form,'input',()=>{
|
|
376
|
+
clearTimeout(t);
|
|
377
|
+
t=setTimeout(save,delay);
|
|
378
|
+
});
|
|
379
|
+
},
|
|
380
|
+
write(form,data){
|
|
381
|
+
Object.entries(data).forEach(([k,v])=>{
|
|
382
|
+
let el=form.elements[k];
|
|
383
|
+
if (!el) return;
|
|
384
|
+
if (el.length) [...el].forEach((opt,i)=>
|
|
385
|
+
/*
|
|
386
|
+
* ???: переделать тернарники на внешные переменные для повышения читаемости
|
|
387
|
+
*
|
|
388
|
+
* Код ужасно читать, не отрицаю
|
|
389
|
+
* Но причина сделать так проста - всего 2 строки с простыми условиями
|
|
390
|
+
* Вместо 8 или 14 как у меня получалось ранее
|
|
391
|
+
*/
|
|
392
|
+
opt[['checkbox','radio'].includes(opt.type) ? 'checked' : 'selected']=
|
|
393
|
+
Array.isArray(v) ? v.includes(opt.value) : opt.value== v);
|
|
394
|
+
else
|
|
395
|
+
el.value=v;
|
|
396
|
+
});
|
|
397
|
+
return data;
|
|
398
|
+
},
|
|
399
|
+
},
|
|
400
|
+
storage:class{
|
|
222
401
|
constructor(strg,name){
|
|
223
402
|
this._=strg;
|
|
224
403
|
this.n=name;
|
|
@@ -229,51 +408,45 @@ _.storage=class{
|
|
|
229
408
|
clear=()=>Object.keys(this._)
|
|
230
409
|
.filter(k=>k.startsWith(this.n))
|
|
231
410
|
.forEach(k=>this._.removeItem(k));
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
console.error(err);
|
|
250
|
-
_.err.log(
|
|
251
|
-
`PROMISE ERROR\n`+
|
|
252
|
-
`${e.stack || e}`
|
|
253
|
-
);
|
|
254
|
-
},
|
|
255
|
-
};
|
|
256
|
-
_.hotkeys={
|
|
411
|
+
},
|
|
412
|
+
hotkeys:{
|
|
413
|
+
/*
|
|
414
|
+
* ГОРЯЧИЕ КЛАВИШЫ
|
|
415
|
+
*
|
|
416
|
+
* Реализует самый настоящий press/release интерфейс
|
|
417
|
+
* Если верить минификатору, после сжатия весит всего 790 байт
|
|
418
|
+
*
|
|
419
|
+
* В Object Hub уже есть текстовый редактор горячих клавиш
|
|
420
|
+
* На базе этого движка, конечно давать textarea с js кодом...
|
|
421
|
+
* Не самая безопасная затея, но как факт кастомизация широчайшая
|
|
422
|
+
*
|
|
423
|
+
* _holds работает не на массивах а на new Set()
|
|
424
|
+
* Сеты работают намного быстрее при большом объёме данных
|
|
425
|
+
* Вы же не хотите чтобы у вас тормозил поток с 100+ хоткеями
|
|
426
|
+
* Из-за простого печатанья?
|
|
427
|
+
*/
|
|
257
428
|
keys:{},
|
|
258
429
|
_holds:new Set(),
|
|
259
430
|
_:false,
|
|
260
431
|
|
|
261
432
|
_parse:combo=>combo.split('+').map(k=>k.trim()),
|
|
262
433
|
_match(keys) {
|
|
434
|
+
// Нужно сверять все клавишы, это же КОМБИНАЦИЯ а не отдельные куски
|
|
263
435
|
for (let k of keys) if (!this._holds.has(k)) return false;
|
|
264
436
|
return true;
|
|
265
437
|
},
|
|
266
438
|
_init() {
|
|
267
439
|
if (this._) return;
|
|
268
440
|
_.$.on(_.$.D,'keydown',e=>{
|
|
269
|
-
this._holds.add(e.code)
|
|
441
|
+
this._holds.add(e.code);// key зависит от раскладки (на Qwerty 'KeyZ' — это 'z', на Йцукен — 'я')
|
|
442
|
+
// code даёт физическое положение клавиши, что важно для игр и хоткеев, и в целом универсальнее
|
|
270
443
|
|
|
271
444
|
for (let combo in this.keys) {
|
|
272
445
|
let h=this.keys[combo];
|
|
273
446
|
if (!this._match(h.keys)) continue;
|
|
274
447
|
|
|
275
448
|
if (h.press && !h.active) {
|
|
276
|
-
h.active=true;
|
|
449
|
+
h.active=true; // active защищает от множественных срабатываний
|
|
277
450
|
h.press(e);
|
|
278
451
|
}
|
|
279
452
|
}
|
|
@@ -290,6 +463,10 @@ _.hotkeys={
|
|
|
290
463
|
}
|
|
291
464
|
});
|
|
292
465
|
_.$.on(window,'blur',()=>{
|
|
466
|
+
/*
|
|
467
|
+
* При переключении в другое окно автоматического keyup не будет
|
|
468
|
+
* Поэтому сбрасываем всё принудительно, мало ли
|
|
469
|
+
*/
|
|
293
470
|
for (let combo in this.keys) {
|
|
294
471
|
let h=this.keys[combo];
|
|
295
472
|
if (h.active) {
|
|
@@ -307,6 +484,7 @@ _.hotkeys={
|
|
|
307
484
|
|
|
308
485
|
this.keys[combo]={
|
|
309
486
|
keys,
|
|
487
|
+
// press/releace по умолчанию пустышки для сокращения синаксиса
|
|
310
488
|
press: press || (()=>{}),
|
|
311
489
|
release: release || (()=>{}),
|
|
312
490
|
active: false
|
|
@@ -317,9 +495,33 @@ _.hotkeys={
|
|
|
317
495
|
off(combo) {
|
|
318
496
|
delete this.keys[combo];
|
|
319
497
|
return this;
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
|
|
498
|
+
},
|
|
499
|
+
},
|
|
500
|
+
win:{
|
|
501
|
+
/*
|
|
502
|
+
* МОДУЛЬ ОКОН
|
|
503
|
+
* если вы спросите почему ньюхелпер я отвечу
|
|
504
|
+
* winBox.js это 35 килобайт, здесь же вы получаете в 25 килобайт
|
|
505
|
+
* И более широкий движок окон и документацию уровня...
|
|
506
|
+
* А у кого нибуть вообще есть такие подробные документации в вебе?
|
|
507
|
+
*
|
|
508
|
+
* Реализует ограниченно-гибкий движок окон, функционал:
|
|
509
|
+
* - открытие, разворот на весь экран, закрытие
|
|
510
|
+
* - сворачивание в таскбар и разворчаивание
|
|
511
|
+
* - нативный css-ресайз (resize:both)
|
|
512
|
+
* - возможность двигать окна (работает на телефонах, я проверял)
|
|
513
|
+
* - сохранение и загрузка окон по вашему выбору
|
|
514
|
+
*
|
|
515
|
+
* !!!: _opn() и toggleFull() могут сломать ваши окна!
|
|
516
|
+
* Эти функции высчитывают координаты окна, и размер окна с учётом padding'а
|
|
517
|
+
* Ни за что не вешайте на ваши окна transform:translate()!
|
|
518
|
+
*
|
|
519
|
+
* !!!: _opn() по умолчанию открывает окно по центру экрана
|
|
520
|
+
* Если не идёт восстановление через write()
|
|
521
|
+
* ???: стоит ли открывать окно в центре, или лучше дать "дефолтную функцию" позиционирования
|
|
522
|
+
*
|
|
523
|
+
* теперь мне надо вспомнить я рефакторил этот код 4 раза или 7 раз
|
|
524
|
+
*/
|
|
323
525
|
manager:false,
|
|
324
526
|
hider:false,
|
|
325
527
|
|
|
@@ -331,9 +533,9 @@ _.win={
|
|
|
331
533
|
hiderAttrs:'',
|
|
332
534
|
|
|
333
535
|
defBtns:[
|
|
334
|
-
['–',w=>
|
|
335
|
-
['=',w=>
|
|
336
|
-
['X',w=>
|
|
536
|
+
['–',w=>w.hide()],
|
|
537
|
+
['=',w=>w.toggleFull()],
|
|
538
|
+
['X',w=>w.close()],
|
|
337
539
|
],
|
|
338
540
|
|
|
339
541
|
animOpen:'',
|
|
@@ -342,46 +544,54 @@ _.win={
|
|
|
342
544
|
animShow:'',
|
|
343
545
|
animFullOn:'',
|
|
344
546
|
animFullOff:'',
|
|
547
|
+
_ae:{once:true},
|
|
345
548
|
|
|
346
549
|
_ID(){
|
|
347
550
|
let id;
|
|
551
|
+
// Создаём случайный 6 символьный айди, чтобы каждый раз не совпадало
|
|
552
|
+
// !!!: в теории можно задать любой айди
|
|
553
|
+
// ???: проверить при скольки окнах генератор начинает тормозить
|
|
348
554
|
do id=Math.random().toString(36).substring(2,8);
|
|
349
555
|
while (_.wins[id]);
|
|
350
556
|
return id;
|
|
351
557
|
},
|
|
352
558
|
_winBtn(win,text,func){
|
|
353
|
-
let b=_.html`<button ${
|
|
559
|
+
let b=_.html`<button ${this.btnAttrs}>${text}</button>`;
|
|
354
560
|
_.$.on(b,'click',()=>func(win));
|
|
355
561
|
return b;
|
|
356
562
|
},
|
|
357
563
|
_hiderBtn(win){
|
|
358
564
|
let title=win.langs!== false ? _.lang.win('WINDOW-'+win.langs) : `>${win.name}<`,
|
|
359
|
-
b=_.html`<button id=hider${win.id} ${
|
|
360
|
-
_.$.on(b,'click',()=>
|
|
565
|
+
b=_.html`<button id=hider${win.id} ${this.hiderAttrs}${title}/button>`;
|
|
566
|
+
_.$.on(b,'click',()=>this.show(win));
|
|
361
567
|
return b;
|
|
362
568
|
},
|
|
363
569
|
_initWin(win){
|
|
364
|
-
let
|
|
365
|
-
|
|
570
|
+
let D=_.$.D,
|
|
571
|
+
wEl=win.elem,
|
|
572
|
+
x1=0,y1=0,x2=0,y2=0,
|
|
573
|
+
prevent=e=>e.preventDefault(),
|
|
366
574
|
startW=e=>{
|
|
367
575
|
let targ=e.target;
|
|
576
|
+
// Проверяем куда нажали, если бы мы не проверяли,
|
|
577
|
+
// То драггер не дал бы нам нажать на кнопки или изменить имя окна
|
|
368
578
|
if (['BUTTON','INPUT'].includes(targ.tagName) || targ.closest('button,input')){
|
|
369
579
|
return;
|
|
370
580
|
}
|
|
371
|
-
|
|
581
|
+
this.manager.appendChild(wEl);
|
|
372
582
|
|
|
373
|
-
e
|
|
374
|
-
x2=e.clientX
|
|
375
|
-
y2=e.clientY
|
|
583
|
+
prevent(e);
|
|
584
|
+
x2=e.clientX;
|
|
585
|
+
y2=e.clientY;
|
|
376
586
|
|
|
377
|
-
|
|
587
|
+
D.onpointermove=moveW;
|
|
378
588
|
|
|
379
|
-
|
|
589
|
+
D.onpointerup=D.onpointercancel=stopW;
|
|
380
590
|
},
|
|
381
591
|
moveW=e=>{
|
|
382
|
-
e
|
|
383
|
-
let cX=e.clientX
|
|
384
|
-
cY=e.clientY
|
|
592
|
+
prevent(e);
|
|
593
|
+
let cX=e.clientX,
|
|
594
|
+
cY=e.clientY;
|
|
385
595
|
|
|
386
596
|
x1=x2 - cX;
|
|
387
597
|
y1=y2 - cY;
|
|
@@ -391,83 +601,97 @@ _.win={
|
|
|
391
601
|
wEl.style.top=(wEl.offsetTop - y1) + "px";
|
|
392
602
|
wEl.style.left=(wEl.offsetLeft - x1) + "px";
|
|
393
603
|
},
|
|
394
|
-
stopW=()=>
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
drag=win.drag;
|
|
399
|
-
drag.onmousedown=drag.ontouchstart=startW;
|
|
604
|
+
stopW=()=>['move','up','cancel'].map(e=>D['onpointer'+e]=null),
|
|
605
|
+
dr=win.drag;
|
|
606
|
+
dr.onpointerdown=startW;
|
|
607
|
+
dr.ontouchmove=prevent;
|
|
400
608
|
},
|
|
401
609
|
open(name,content='',customAttrs=''){
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
610
|
+
let winId=this._ID(),
|
|
611
|
+
winState=_.wins[winId]={
|
|
612
|
+
id:winId,
|
|
613
|
+
name:name,
|
|
614
|
+
langs:name,
|
|
615
|
+
state:'opened',
|
|
616
|
+
full:false,
|
|
617
|
+
inRename:false,
|
|
618
|
+
// Если окно новое, координаты полностью нулевые,
|
|
619
|
+
// Нужно чтобы проверять создаётся ли окно и если да то задавать координаты
|
|
620
|
+
onUnfull:{top:0,left:0,width:0,height:0},
|
|
621
|
+
attrs:customAttrs,
|
|
622
|
+
elem:false,
|
|
623
|
+
drag:false,
|
|
624
|
+
content:false,
|
|
625
|
+
};
|
|
626
|
+
return this._opn(winState,content);
|
|
627
|
+
},
|
|
628
|
+
_opn(w,content=''){
|
|
629
|
+
if (!this.manager || !this.hider) throw new Error('Window managers not inited');
|
|
630
|
+
|
|
631
|
+
let wId=w.id,
|
|
632
|
+
html=
|
|
633
|
+
_.html`<div id=${wId} ${this.winAttrs} ${w.attrs}>
|
|
423
634
|
<div style="display:flex;justify-content:space-between;align-items:center"
|
|
424
|
-
${
|
|
425
|
-
<span ${
|
|
426
|
-
<div id=btns${
|
|
635
|
+
${this.dragAttrs} id=DRAGGER${wId}>
|
|
636
|
+
<span ${this.titleAttrs} id=title${wId}${_.lang.win('WINDOW-'+w.name)}/span>
|
|
637
|
+
<div id=btns${wId}></div>
|
|
427
638
|
</div>
|
|
428
|
-
<div id=content${
|
|
429
|
-
${content.replace(/\{winId\}/g,
|
|
639
|
+
<div id=content${wId} style=overflow:auto;width:100%;height:100%>
|
|
640
|
+
${content.replace(/\{winId\}/g,wId)}
|
|
430
641
|
</div>
|
|
431
642
|
</div>`,
|
|
432
|
-
btns=_.$.q(`#btns${
|
|
433
|
-
for(let b of
|
|
434
|
-
|
|
643
|
+
btns=_.$.q(`#btns${wId}`,html);
|
|
644
|
+
for(let b of this.defBtns) btns.append(this._winBtn(w,...b));
|
|
435
645
|
html.style.overflow='hidden';
|
|
436
646
|
html.style.resize='both';
|
|
437
647
|
|
|
438
|
-
let anim=
|
|
648
|
+
let anim=this.animOpen;
|
|
439
649
|
if (anim)
|
|
440
|
-
_.$.on(html,'animationend',()=>html.classList.remove(anim),
|
|
441
|
-
_.win.
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
if (
|
|
455
|
-
|
|
456
|
-
|
|
650
|
+
_.$.on(html,'animationend',()=>html.classList.remove(anim),this._ae);
|
|
651
|
+
w.setTitle=nT=>_.win.setTitle(w,nT);
|
|
652
|
+
w.toggleFull=e=>_.win.toggleFull(w);
|
|
653
|
+
w.close=e=>_.win.close(w);
|
|
654
|
+
w.hide=e=>_.win.hide(w);
|
|
655
|
+
w.show=e=>_.win.show(w);
|
|
656
|
+
this.manager.append(html);
|
|
657
|
+
|
|
658
|
+
let win=w.elem=_.$.id(wId),
|
|
659
|
+
c=_.$.cliRect(_.$.id('content'+wId)),r=_.$.cliRect(win),
|
|
660
|
+
padX=r.width - c.width,padY=r.height - c.height;
|
|
661
|
+
w.drag=_.$.id('DRAGGER'+wId);
|
|
662
|
+
w.content=_.$.id('content'+wId);
|
|
663
|
+
|
|
664
|
+
if (w.onUnfull.width === 0) {
|
|
665
|
+
// Здесь и задаются координаты...
|
|
666
|
+
// Мастера клин кода не выносите мне мозги прошу
|
|
667
|
+
// Оно же работает!!!
|
|
668
|
+
if (!w.attrs.includes('top')) {
|
|
669
|
+
win.style.top=win.offsetTop - (win.offsetHeight / 2) + 'px';
|
|
670
|
+
win.style.left=win.offsetLeft - (win.offsetWidth / 2) + 'px';
|
|
671
|
+
}
|
|
672
|
+
if (!w.attrs.includes('width')) win.style.height=(win.offsetHeight - padX) + 'px';
|
|
673
|
+
if (!w.attrs.includes('height')) win.style.width=(win.offsetWidth - padY) + 'px';
|
|
674
|
+
} else
|
|
675
|
+
for (let pos in w.onUnfull)
|
|
676
|
+
win.style[pos] = w.onUnfull[pos] + 'px'
|
|
677
|
+
|
|
678
|
+
this._initWin(w);
|
|
679
|
+
_.$.on(w.drag,'contextmenu',(e)=>{
|
|
680
|
+
e.preventDefault();
|
|
457
681
|
if(e.target.closest('button')) return;
|
|
458
|
-
let wT=_.$.id('title'+
|
|
459
|
-
if (!
|
|
460
|
-
wT.innerHTML=`<input ${
|
|
461
|
-
|
|
682
|
+
let wT=_.$.id('title'+wId);
|
|
683
|
+
if (!w.inRename){
|
|
684
|
+
wT.innerHTML=`<input ${this.renameAttrs} id=rename${wId} value="${wT.textContent}">`;
|
|
685
|
+
w.inRename=true;
|
|
462
686
|
}else{
|
|
463
|
-
|
|
464
|
-
|
|
687
|
+
this.setTitle(w,_.$.id('rename'+wId).value);
|
|
688
|
+
w.inRename=false;
|
|
465
689
|
}
|
|
466
690
|
});
|
|
467
691
|
|
|
468
|
-
|
|
692
|
+
if (w.state === 'hidened') w.hide();
|
|
469
693
|
|
|
470
|
-
return
|
|
694
|
+
return w;
|
|
471
695
|
},
|
|
472
696
|
setTitle(win,newT){
|
|
473
697
|
win.langs=false;
|
|
@@ -485,12 +709,12 @@ _.win={
|
|
|
485
709
|
let wEl=win.elem,
|
|
486
710
|
ws=wEl.style,
|
|
487
711
|
wc=wEl.classList,
|
|
488
|
-
cont=_.$.id('content'+win.id)
|
|
489
|
-
rect=wEl
|
|
712
|
+
cont=_.$.cliRect(_.$.id('content'+win.id)),
|
|
713
|
+
rect=_.$.cliRect(wEl),
|
|
490
714
|
padX=rect.width - cont.width,
|
|
491
715
|
padY=rect.height - cont.height,
|
|
492
|
-
aOn=
|
|
493
|
-
aOff=
|
|
716
|
+
aOn=this.animFullOn,
|
|
717
|
+
aOff=this.animFullOff,
|
|
494
718
|
fd={
|
|
495
719
|
top: rect.top, left: rect.left,
|
|
496
720
|
width: cont.width, height: cont.height,
|
|
@@ -509,35 +733,37 @@ _.win={
|
|
|
509
733
|
ws.left=0;
|
|
510
734
|
ws.width=`calc(100% - ${padX}px)`;
|
|
511
735
|
ws.height=`calc(100% - ${padY}px)`;
|
|
512
|
-
win.drag.
|
|
513
|
-
win.drag.ontouchstart=null;
|
|
736
|
+
win.drag.onpointerdown=null;
|
|
514
737
|
},
|
|
515
738
|
doUnful=()=>{
|
|
516
739
|
if (aOff) wc.remove(aOff);
|
|
517
740
|
unful();
|
|
518
741
|
win.full=false;
|
|
519
|
-
|
|
742
|
+
this._initWin(win);
|
|
520
743
|
},
|
|
521
744
|
old=win.onUnfull;
|
|
522
745
|
if (!win.full) {
|
|
523
746
|
if (aOn) {
|
|
524
747
|
wc.add(aOn);
|
|
525
|
-
_.$.on(wEl,'animationend',doFul,
|
|
748
|
+
_.$.on(wEl,'animationend',doFul,this._ae);
|
|
526
749
|
}else doFul();
|
|
527
750
|
} else {
|
|
528
751
|
if (aOff) {
|
|
529
752
|
wc.add(aOff);
|
|
530
753
|
unful();
|
|
531
|
-
_.$.on(wEl,'animationend',doUnful,
|
|
754
|
+
_.$.on(wEl,'animationend',doUnful,this._ae);
|
|
532
755
|
}else doUnful();
|
|
533
756
|
}
|
|
534
757
|
},
|
|
535
758
|
close(win){
|
|
536
759
|
let w=win.elem,
|
|
537
760
|
remover=()=>{
|
|
538
|
-
let
|
|
539
|
-
|
|
540
|
-
|
|
761
|
+
let dr=win.drag,D=_.$.D;
|
|
762
|
+
dr.onpointerdown=dr.ontouchmove=null;
|
|
763
|
+
// Удаляем обработчики висящие на документе
|
|
764
|
+
// Если их не удалять рано или поздно случится утечка памяти
|
|
765
|
+
// Я не знаю как я жил во времена 2.0 когда движок только появился
|
|
766
|
+
['move','up','cancel'].map(e=>D['onpointer'+e]=null);
|
|
541
767
|
w.remove();
|
|
542
768
|
delete _.wins[win.id];
|
|
543
769
|
};
|
|
@@ -545,34 +771,34 @@ _.win={
|
|
|
545
771
|
_.$.id('hider'+win.id).remove();
|
|
546
772
|
remover();
|
|
547
773
|
}else{
|
|
548
|
-
let anim=
|
|
774
|
+
let anim=this.animClose;
|
|
549
775
|
if(anim){
|
|
550
776
|
w.classList.add(anim);
|
|
551
|
-
_.$.on(w,'animationend',remover,
|
|
777
|
+
_.$.on(w,'animationend',remover,this._ae);
|
|
552
778
|
}else
|
|
553
779
|
remover();
|
|
554
780
|
}
|
|
555
781
|
},
|
|
556
782
|
hide(win){
|
|
557
|
-
let wEl
|
|
783
|
+
let wEl=win.elem,
|
|
558
784
|
wc=wEl.classList,
|
|
559
|
-
anim=
|
|
785
|
+
anim=this.animHide,
|
|
560
786
|
hider=()=>{
|
|
561
787
|
wEl.style.display='none';
|
|
562
788
|
if(anim)wc.remove(anim);
|
|
563
789
|
win.state='hidened';
|
|
564
|
-
|
|
790
|
+
this.hider.append(this._hiderBtn(win));
|
|
565
791
|
}
|
|
566
792
|
if(anim){
|
|
567
793
|
wc.add(anim);
|
|
568
|
-
_.$.on(wEl,'animationend',hider,
|
|
794
|
+
_.$.on(wEl,'animationend',hider,this._ae);
|
|
569
795
|
}else
|
|
570
796
|
hider();
|
|
571
797
|
},
|
|
572
798
|
show(win){
|
|
573
|
-
let wEl
|
|
799
|
+
let wEl=win.elem,
|
|
574
800
|
wc=wEl.classList,
|
|
575
|
-
anim=
|
|
801
|
+
anim=this.animShow,
|
|
576
802
|
hider=_.$.id('hider'+win.id),
|
|
577
803
|
shower=()=>{
|
|
578
804
|
if(anim)wc.remove(anim);
|
|
@@ -582,22 +808,63 @@ _.win={
|
|
|
582
808
|
hider.remove();
|
|
583
809
|
if(anim){
|
|
584
810
|
wc.add(anim);
|
|
585
|
-
_.$.on(wEl,'animationend',shower,
|
|
811
|
+
_.$.on(wEl,'animationend',shower,this._ae);
|
|
586
812
|
}else
|
|
587
813
|
shower()
|
|
588
814
|
},
|
|
815
|
+
read(){
|
|
816
|
+
let store = {};
|
|
817
|
+
for (let winId in _.wins) {
|
|
818
|
+
let win={..._.wins[winId]},
|
|
819
|
+
size=win.onUnfull,
|
|
820
|
+
wEl = win.elem,
|
|
821
|
+
c=_.$.cliRect(win.content),r=_.$.cliRect(wEl);
|
|
822
|
+
win.realContent=win.content.innerHTML;
|
|
823
|
+
size.top=r.top;
|
|
824
|
+
size.left=r.left;
|
|
825
|
+
size.height=wEl.offsetHeight - (r.height - c.height);
|
|
826
|
+
size.width=wEl.offsetWidth - (r.width - c.width);
|
|
827
|
+
delete win.elem;
|
|
828
|
+
delete win.drag;
|
|
829
|
+
delete win.content;
|
|
830
|
+
store[winId] = win;
|
|
831
|
+
}
|
|
832
|
+
return store;
|
|
833
|
+
},
|
|
834
|
+
write(state){
|
|
835
|
+
for (let winId in state) {
|
|
836
|
+
let win=state[winId],
|
|
837
|
+
content=win.realContent;
|
|
838
|
+
delete win.realContent;
|
|
839
|
+
_.wins[winId] = win;
|
|
840
|
+
this._opn(win,content);
|
|
841
|
+
}
|
|
842
|
+
},
|
|
843
|
+
},
|
|
844
|
+
wins:{},
|
|
589
845
|
};
|
|
590
|
-
_.wins={};
|
|
591
846
|
|
|
592
|
-
_.$.on(window,'error',_.err.handleGlobal);
|
|
593
|
-
_.$.on(window,'unhandledrejection',_.err.handleRejection);
|
|
594
847
|
_.$.on(window,'popstate',()=>{
|
|
595
848
|
let l=_.link
|
|
849
|
+
/*
|
|
850
|
+
* popstate срабатывает когда:
|
|
851
|
+
* - пользователь прыгает по истории назад/вперёд
|
|
852
|
+
* - мы вызываем history.pushState (не replaceState)
|
|
853
|
+
*
|
|
854
|
+
* _i различает эти случаи:
|
|
855
|
+
* true = пользователь прыгнул назад
|
|
856
|
+
* false = страница пишет свой адрес в ссылку
|
|
857
|
+
*/
|
|
858
|
+
// ???: некоторые браузеры могут вызывать popstate и при реплейсе
|
|
596
859
|
if (!l._i) {
|
|
860
|
+
// здесь происходит перенос команд при popstate
|
|
861
|
+
// читайте _.lang.get() если хотите узнать почему
|
|
597
862
|
let nUrl='?' + [l.compile()[0],...l._cmd].join('&');
|
|
598
|
-
history.replaceState(null,null,nUrl);
|
|
599
863
|
l._i=true;
|
|
864
|
+
history.replaceState(null,null,nUrl);
|
|
600
865
|
l.get();
|
|
601
866
|
} else
|
|
602
867
|
l._i=false;
|
|
603
868
|
});
|
|
869
|
+
|
|
870
|
+
return _}();
|