newhelper-js 2.1.2 → 2.1.4
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/docs.md +1 -1
- package/newHelper.js +542 -370
- package/package.json +1 -1
package/docs.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
Базовая документация (если поможет)
|
|
2
|
-
- Внимание! документация предоставлена к внутренней версии 2.0.9, возможны расхождения с версией 2.1, рекомендуем сверяться с исходным кодом
|
|
2
|
+
- Внимание! документация предоставлена к внутренней версии 2.0.9, возможны расхождения с версией 2.1.4, рекомендуем сверяться с исходным кодом
|
|
3
3
|
|
|
4
4
|
# Роутер (_.link)
|
|
5
5
|
Роутер здесь завязан на особенном от популярных решений поведении. Берётся query строка, разбивается по "&", и из полученного массива дальше с этой строкой роутер работает обособленно. Сначала идёт элемент 0 который отвечает за страницы и вложенность, последующие элементы являются командами, на них вы можете навешать открытие окон, модалки, или просто всякие дебаг штучки (например отключение проверки наличия капчи при логине или смену языка)
|
package/newHelper.js
CHANGED
|
@@ -1,25 +1,31 @@
|
|
|
1
1
|
/*
|
|
2
|
+
* Перед вами код newHelper.js версии 2.1.4, он построен на базе фабрики
|
|
3
|
+
* Которая начинается с Intl.newHelper=function(){...};
|
|
4
|
+
* Причина использоватся Intl.newHelper банально проста
|
|
5
|
+
* Если я сейчас засираю глобалскоуп одной полу гибкой переменной
|
|
6
|
+
* То почему бы не начать отказываться от засирания глобал скоупа как такового
|
|
7
|
+
* И да, для инициализации ньюхелпера реально нужно писать
|
|
8
|
+
* yourVariable = Intl.newHelper()
|
|
9
|
+
*
|
|
2
10
|
* Стиль комментариев
|
|
3
11
|
* FIXME - странное поведение функции, которое желательно бы переделать
|
|
4
12
|
* ??? - требует уточнения
|
|
5
13
|
* !!! - обратите внимание
|
|
6
14
|
*
|
|
7
|
-
*
|
|
8
|
-
* Которая начинается с _=function(){...}();
|
|
9
|
-
* Если у вас есть конфликты с Lodash вы можете переименовать _ на всё что вам нужно
|
|
15
|
+
* ???: рассмотреть переход на es6 экспорт вместо вкладывания фабрики в Intl
|
|
10
16
|
*
|
|
11
|
-
*
|
|
12
|
-
* модуль _.autoForm
|
|
13
|
-
* _.win.read()
|
|
14
|
-
* _.win.write()
|
|
17
|
+
* ???: Последний глобальный эвент лисенер можно вынести внутрь _.link.get()
|
|
15
18
|
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
19
|
+
* Новые модули, готовятсяк релизу в 2.2
|
|
20
|
+
* их апи может быть чуть чуть нестабильно:
|
|
21
|
+
* form
|
|
22
|
+
* tables
|
|
23
|
+
* drag (портирован из окон)
|
|
24
|
+
* pipe/pipeAsync
|
|
18
25
|
*/
|
|
19
26
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
link:{
|
|
27
|
+
Intl.newHelper=function() {let _ = {
|
|
28
|
+
link: {
|
|
23
29
|
/*
|
|
24
30
|
* МОДУЛЬ ССЫЛОК
|
|
25
31
|
*
|
|
@@ -34,41 +40,41 @@ link:{
|
|
|
34
40
|
*
|
|
35
41
|
* !!!: в функции get() работает весь роутинг, в т.ч. вложенный для страниц
|
|
36
42
|
*/
|
|
37
|
-
basePage:()=>{},
|
|
38
|
-
defTitle:'',
|
|
39
|
-
actions:{},
|
|
40
|
-
commands:{},
|
|
43
|
+
basePage: ()=>{},
|
|
44
|
+
defTitle: '',
|
|
45
|
+
actions: {},
|
|
46
|
+
commands: {},
|
|
41
47
|
|
|
42
48
|
_i: true, // _i - блокировщик pushState в set()
|
|
43
|
-
_cmd:[],
|
|
44
|
-
compile:()=>location.search.replace('?','').split('&'),
|
|
45
|
-
set(page,title=this.defTitle){
|
|
46
|
-
if (title)
|
|
47
|
-
if (!this._i){
|
|
48
|
-
let link=this.compile();
|
|
49
|
-
link[0]=page;
|
|
49
|
+
_cmd: [],
|
|
50
|
+
compile: ()=>location.search.replace('?','').split('&'),
|
|
51
|
+
set(page, title = this.defTitle) {
|
|
52
|
+
if (title) document.title = title;
|
|
53
|
+
if (!this._i) {
|
|
54
|
+
let link = this.compile();
|
|
55
|
+
link[0] = page;
|
|
50
56
|
history.pushState(null,null,'?'+link.join('&'));
|
|
51
57
|
}
|
|
52
|
-
this._i=false;
|
|
58
|
+
this._i = false;
|
|
53
59
|
},
|
|
54
|
-
add(cmd){
|
|
55
|
-
let link=this.compile();
|
|
56
|
-
if (!link.includes(cmd)){
|
|
60
|
+
add(cmd) {
|
|
61
|
+
let link = this.compile();
|
|
62
|
+
if (!link.includes(cmd)) {
|
|
57
63
|
link.push(cmd);
|
|
58
64
|
this._cmd.push(cmd);
|
|
59
65
|
history.replaceState(null,null,'?'+link.join('&'));
|
|
60
66
|
}
|
|
61
67
|
},
|
|
62
|
-
remove(cmd){
|
|
63
|
-
let link=this.compile();
|
|
68
|
+
remove(cmd) {
|
|
69
|
+
let link = this.compile();
|
|
64
70
|
if (link.includes(cmd)){
|
|
65
|
-
let c=this._cmd;
|
|
71
|
+
let c = this._cmd;
|
|
66
72
|
link.splice(link.indexOf(cmd),1);
|
|
67
73
|
c.splice(c.indexOf(cmd),1);
|
|
68
74
|
history.replaceState(null,null,'?'+link.join('&'));
|
|
69
75
|
}
|
|
70
76
|
},
|
|
71
|
-
get(){
|
|
77
|
+
get() {
|
|
72
78
|
/*
|
|
73
79
|
* Страницы бросают ошибку чтобы вызвать базовую страницу
|
|
74
80
|
* Команды тем временем так не делают
|
|
@@ -77,40 +83,42 @@ link:{
|
|
|
77
83
|
* При popstate команды берутся из хранилища _cmd, вместо самой ссылки
|
|
78
84
|
* Сделано это для переноса команд при прыжках по истории
|
|
79
85
|
*/
|
|
80
|
-
let links=this.compile(),
|
|
81
|
-
[firstKey,fisrtValue]=links[0].split('='),
|
|
82
|
-
cmds=links.slice(1);
|
|
86
|
+
let links = this.compile(),
|
|
87
|
+
[ firstKey, fisrtValue ] = links[0].split('='),
|
|
88
|
+
cmds = links.slice(1);
|
|
83
89
|
try {
|
|
84
|
-
let dirs=firstKey.split('/'),
|
|
85
|
-
dir=this.actions,
|
|
86
|
-
main=dir[firstKey];
|
|
87
|
-
if (!firstKey.includes('/')){
|
|
90
|
+
let dirs = firstKey.split('/'),
|
|
91
|
+
dir = this.actions,
|
|
92
|
+
main = dir[firstKey];
|
|
93
|
+
if (!firstKey.includes('/')) {
|
|
88
94
|
main(fisrtValue);
|
|
89
95
|
} else {
|
|
90
96
|
for (let p of dirs){
|
|
91
|
-
let kDir=dir[p+'/'];
|
|
97
|
+
let kDir = dir[p+'/'];
|
|
92
98
|
if (kDir)
|
|
93
|
-
dir=kDir;
|
|
99
|
+
dir = kDir;
|
|
94
100
|
else{
|
|
95
101
|
dir[p](fisrtValue);
|
|
96
102
|
break;
|
|
97
103
|
}
|
|
98
104
|
}
|
|
99
105
|
}
|
|
100
|
-
}catch(e){
|
|
106
|
+
} catch (e) {
|
|
101
107
|
this.basePage();
|
|
102
108
|
throw e;
|
|
103
109
|
}
|
|
104
|
-
this._cmd=cmds;
|
|
105
|
-
cmds.forEach(
|
|
106
|
-
let [key,value]=
|
|
107
|
-
let cmd=this.commands[key];
|
|
108
|
-
if (cmd)
|
|
109
|
-
|
|
110
|
+
this._cmd = cmds;
|
|
111
|
+
cmds.forEach(cmdPre => {
|
|
112
|
+
let [ key, value ] = cmdPre.split('=');
|
|
113
|
+
let cmd = this.commands[key];
|
|
114
|
+
if (cmd)
|
|
115
|
+
cmd(value);
|
|
116
|
+
else
|
|
117
|
+
console.error(new Error(`command '${cmd}' doesn't exist!`))
|
|
110
118
|
});
|
|
111
119
|
},
|
|
112
120
|
},
|
|
113
|
-
lazy:{
|
|
121
|
+
lazy: {
|
|
114
122
|
/*
|
|
115
123
|
* МОДУЛЬ ЛЕНИ
|
|
116
124
|
*
|
|
@@ -123,12 +131,8 @@ lazy:{
|
|
|
123
131
|
*
|
|
124
132
|
* ???: будет ли легче создавать лень в легаси проектах через es6 импорты
|
|
125
133
|
*/
|
|
126
|
-
loaded:{},
|
|
127
|
-
load(url
|
|
128
|
-
let key=url.split('?')[0], // отсекаем параметры, чтобы не дублировать
|
|
129
|
-
state=this.loaded,
|
|
130
|
-
c=state[key];
|
|
131
|
-
|
|
134
|
+
loaded: {},
|
|
135
|
+
load(url, ...args) {
|
|
132
136
|
/*
|
|
133
137
|
* ...args передаются в Promise.resolve(args)
|
|
134
138
|
* Это позволяет делать _.lazy.load('script.js', 'данные', 'для', 'колбека')
|
|
@@ -141,45 +145,54 @@ lazy:{
|
|
|
141
145
|
*
|
|
142
146
|
* Это защита от двойной загрузки одного скрипта
|
|
143
147
|
*/
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
148
|
+
let key = url.split('?')[0], // отсекаем параметры, чтобы не дублировать
|
|
149
|
+
state = this.loaded;
|
|
150
|
+
if (state[key] === true)
|
|
151
|
+
return Promise.resolve(args);
|
|
152
|
+
if (state[key] instanceof Promise)
|
|
153
|
+
return state[key].then(()=>args);
|
|
154
|
+
|
|
155
|
+
let promise = new Promise((resolve,reject)=>{
|
|
156
|
+
let scr = document.createElement('script');
|
|
157
|
+
scr.src = url;
|
|
158
|
+
scr.onload = ()=>{
|
|
159
|
+
state[key] = true;
|
|
152
160
|
resolve(args);
|
|
153
161
|
};
|
|
154
|
-
scr.onerror=()=>{
|
|
162
|
+
scr.onerror = ()=>{
|
|
155
163
|
delete state[key];
|
|
156
164
|
reject(new Error('Failed to load '+url));
|
|
157
165
|
};
|
|
158
|
-
|
|
166
|
+
document.head.append(scr);
|
|
159
167
|
});
|
|
160
|
-
state[key]=
|
|
161
|
-
return
|
|
168
|
+
state[key] = promise;
|
|
169
|
+
return promise;
|
|
162
170
|
},
|
|
163
|
-
register(
|
|
164
|
-
if (!Array.isArray(funcs))
|
|
171
|
+
register(script, funcs) {
|
|
172
|
+
if (!Array.isArray(funcs))
|
|
173
|
+
return new Error('Array required for register');
|
|
165
174
|
|
|
166
|
-
for (let fn of funcs)
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
175
|
+
for (let fn of funcs) {
|
|
176
|
+
let fns = fn.split('.'),
|
|
177
|
+
method = fns.pop(),
|
|
178
|
+
path = fns.slice(0,-1);
|
|
179
|
+
console.log(path)
|
|
180
|
+
window[fn] = (...a)=>
|
|
181
|
+
this.lazy(script,fn).then(f=>f(...a));
|
|
182
|
+
}
|
|
172
183
|
},
|
|
173
|
-
async
|
|
174
|
-
let w=window,
|
|
175
|
-
wrapper=w[fn];
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
184
|
+
async lazy(scr, fn) {
|
|
185
|
+
let w = window,
|
|
186
|
+
wrapper = w[fn];
|
|
187
|
+
|
|
188
|
+
await this.load(scr); // await короче Promise.then
|
|
189
|
+
|
|
190
|
+
if (wrapper !== w[fn])
|
|
191
|
+
return w[fn];
|
|
179
192
|
throw new Error(`Function ${fn} not loaded from ${scr}`);
|
|
180
193
|
},
|
|
181
194
|
},
|
|
182
|
-
lang:{
|
|
195
|
+
lang: {
|
|
183
196
|
/*
|
|
184
197
|
* МОДУЛЬ ПЕРЕВОДОВ
|
|
185
198
|
*
|
|
@@ -191,44 +204,35 @@ lang:{
|
|
|
191
204
|
* Так удобнее отображать динамичные данные на сайтах
|
|
192
205
|
* Например никнейм пользователя
|
|
193
206
|
*/
|
|
194
|
-
addr:'',
|
|
195
|
-
vars:{},
|
|
196
|
-
main:{},
|
|
197
|
-
|
|
198
|
-
load(name)
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
},
|
|
205
|
-
parse:(packet,vars=_.lang.vars)=>
|
|
206
|
-
/*
|
|
207
|
-
* Регулярка ищет всё, что внутри плюсов, в JSON они редки
|
|
208
|
-
* Это позволяет без конфликтов подставлять переменные если они нашлись
|
|
209
|
-
*
|
|
210
|
-
* ???: переделать под общий синтаксис типа {var}
|
|
211
|
-
*/
|
|
212
|
-
packet.replace(/\+([^+]+)\+/g,(match,key)=>{
|
|
213
|
-
let v=vars[key];
|
|
214
|
-
return v!== undefined ? v : match;
|
|
207
|
+
addr: '',
|
|
208
|
+
vars: {},
|
|
209
|
+
main: {},
|
|
210
|
+
|
|
211
|
+
load: name => fetch(_.lang.addr + name + '.json'),
|
|
212
|
+
parse: (packet, vars = _.lang.vars)=>
|
|
213
|
+
// ???: переделать под общий синтаксис типа {var}
|
|
214
|
+
packet.replace(/\+([^+]+)\+/g, (match, key)=>{
|
|
215
|
+
let v = vars[key];
|
|
216
|
+
return v !== undefined ? v : match;
|
|
215
217
|
}),
|
|
216
218
|
async replace(name){
|
|
217
|
-
const
|
|
218
|
-
this.main=JSON.parse(this.parse(
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
tag=
|
|
224
|
-
|
|
225
|
-
if (tag=== 'IMG')
|
|
219
|
+
const packet = await this.load(name);
|
|
220
|
+
this.main = JSON.parse(this.parse(packet)); // без замены языка нельзя начинать перевод
|
|
221
|
+
|
|
222
|
+
for (let el of document.querySelectorAll('[data-trans]')) {
|
|
223
|
+
let key = el.dataset.trans,
|
|
224
|
+
text = this.main[key] || key,
|
|
225
|
+
tag = el.tagName;
|
|
226
|
+
|
|
227
|
+
if (tag === 'IMG')
|
|
228
|
+
el.src = text;
|
|
226
229
|
else if (['INPUT','TEXTAREA'].includes(tag))
|
|
227
|
-
|
|
228
|
-
else
|
|
230
|
+
el[ el.type === 'submit' ? 'value' : 'placeholder' ] = text;
|
|
231
|
+
else
|
|
232
|
+
el.innerHTML = text;
|
|
229
233
|
}
|
|
230
234
|
// возвращаем для последующей обработки пакета, например для сохранения в _.storage
|
|
231
|
-
return
|
|
235
|
+
return packet;
|
|
232
236
|
},
|
|
233
237
|
|
|
234
238
|
/*
|
|
@@ -242,30 +246,25 @@ lang:{
|
|
|
242
246
|
*
|
|
243
247
|
* !!!: если ключа в пакете нету, будет выброшен warning
|
|
244
248
|
*/
|
|
245
|
-
attr:
|
|
246
|
-
from:i=>_.lang.main[i] || console.warn(`_.lang> ${i} is undefined`) || i,
|
|
249
|
+
attr: i=>` data-trans="${i}"`,
|
|
250
|
+
from: i=>_.lang.main[i] || console.warn(`_.lang> ${i} is undefined`) || i,
|
|
247
251
|
|
|
248
252
|
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
|
-
*/
|
|
253
|
+
submit: i=>_.lang.attr(i)+`value="${_.lang.from(i)}">`, // <input type=submit>
|
|
255
254
|
input: i=>_.lang.attr(i)+`placeholder="${_.lang.from(i)}">`,
|
|
256
255
|
textarea: i=>_.lang.attr(i)+`placeholder="${_.lang.from(i)}"><`,
|
|
257
256
|
img: i=>_.lang.attr(i)+`src="${_.lang.from(i)}"`,
|
|
258
|
-
|
|
259
|
-
let text=this.from(i),
|
|
260
|
-
|
|
261
|
-
if (text== null || text== ''){
|
|
262
|
-
text=i;
|
|
263
|
-
|
|
257
|
+
winTitle(i) {
|
|
258
|
+
let text = this.from(i),
|
|
259
|
+
dataTrans = this.attr(i);
|
|
260
|
+
if (text == null || text == '') {
|
|
261
|
+
text = i;
|
|
262
|
+
dataTrans = '';
|
|
264
263
|
}
|
|
265
|
-
return `${
|
|
264
|
+
return `${dataTrans}>${text}<`;
|
|
266
265
|
},
|
|
267
266
|
},
|
|
268
|
-
http:{
|
|
267
|
+
http: {
|
|
269
268
|
/*
|
|
270
269
|
* HTTP-КЛИЕНТ
|
|
271
270
|
*
|
|
@@ -278,50 +277,46 @@ http:{
|
|
|
278
277
|
* Как пример Authorization: 'your token'
|
|
279
278
|
* ???: добавить возможность игнорировать дефолтные хедеры
|
|
280
279
|
*/
|
|
281
|
-
defaultHeaders:{},
|
|
282
|
-
req(method,url,data='',headers={},fileProgressElement=false){
|
|
283
|
-
return new Promise((resolve,reject)=>{
|
|
284
|
-
let xhr=new XMLHttpRequest();
|
|
280
|
+
defaultHeaders: {},
|
|
281
|
+
req(method, url, data = '', headers = {}, fileProgressElement = false) {
|
|
282
|
+
return new Promise((resolve, reject)=>{
|
|
283
|
+
let xhr = new XMLHttpRequest();
|
|
285
284
|
|
|
286
|
-
xhr.open(method,url);
|
|
285
|
+
xhr.open(method, url);
|
|
287
286
|
|
|
288
|
-
let allHeaders={...this.defaultHeaders
|
|
287
|
+
let allHeaders = { ...this.defaultHeaders, ...headers };
|
|
289
288
|
for (let header in allHeaders)
|
|
290
|
-
xhr.setRequestHeader(header,allHeaders[header]);
|
|
289
|
+
xhr.setRequestHeader(header, allHeaders[header]);
|
|
291
290
|
|
|
292
291
|
// !!!: fileProgressElement ожидает <progress> элемент без min/max
|
|
293
292
|
// Потому что value от 0 до 1
|
|
294
293
|
if (fileProgressElement)
|
|
295
|
-
xhr.upload.onprogress=
|
|
296
|
-
if (e.lengthComputable){
|
|
297
|
-
let percentage=(e.loaded / e.total);
|
|
298
|
-
fileProgressElement.setAttribute('value',percentage);
|
|
294
|
+
xhr.upload.onprogress= e=>{
|
|
295
|
+
if (e.lengthComputable) {
|
|
296
|
+
let percentage = (e.loaded / e.total);
|
|
297
|
+
fileProgressElement.setAttribute('value', percentage);
|
|
299
298
|
}
|
|
300
299
|
};
|
|
301
300
|
|
|
302
|
-
xhr.onreadystatechange=()=>{
|
|
301
|
+
xhr.onreadystatechange= ()=>{
|
|
303
302
|
if (xhr.readyState=== 4)
|
|
304
|
-
if (xhr.status >= 200 && xhr.status < 300)
|
|
305
|
-
|
|
303
|
+
if (xhr.status >= 200 && xhr.status < 300)
|
|
304
|
+
resolve(xhr.response);
|
|
305
|
+
else
|
|
306
|
+
reject(new Error(`${xhr.status} - ${xhr.statusText}`),xhr);
|
|
306
307
|
};
|
|
307
|
-
xhr.onerror=()=>
|
|
308
|
+
xhr.onerror = ()=>
|
|
309
|
+
reject(new Error('Network error'), xhr);
|
|
308
310
|
|
|
309
311
|
xhr.send(data);
|
|
310
312
|
});
|
|
311
313
|
},
|
|
314
|
+
get: (url, headers={})=>
|
|
315
|
+
_.http.req('GET', url, false, headers),
|
|
316
|
+
post: (url, data = '', headers = {}, fileProgressElement = false)=>
|
|
317
|
+
_.http.req('POST', url, data, headers, fileProgressElement)
|
|
312
318
|
},
|
|
313
|
-
|
|
314
|
-
D: document,
|
|
315
|
-
id:(i)=>_.$.D.getElementById(i),
|
|
316
|
-
q:(i,p=_.$.D)=>p.querySelector(i),
|
|
317
|
-
qa:(i,p=_.$.D)=>p.querySelectorAll(i),
|
|
318
|
-
|
|
319
|
-
on:(el,ev,fn,opts)=>el.addEventListener(ev,fn,opts),
|
|
320
|
-
off:(el,ev,fn,opts)=>el.removeEventListener(ev,fn,opts),
|
|
321
|
-
|
|
322
|
-
cliRect:e=>e.getBoundingClientRect(), // сокращение чтобы не писать 25+ символов
|
|
323
|
-
},
|
|
324
|
-
html(strs,...args){
|
|
319
|
+
html(strs, ...args) {
|
|
325
320
|
/*
|
|
326
321
|
* Шаблонные строки в DOM
|
|
327
322
|
*
|
|
@@ -334,82 +329,229 @@ html(strs,...args){
|
|
|
334
329
|
* - Быстрее чем createElement для сложных структур
|
|
335
330
|
* - Банально удобнее createElement для сложных древ
|
|
336
331
|
*/
|
|
337
|
-
let
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
332
|
+
let fullStr = '',
|
|
333
|
+
DOMs = [];
|
|
334
|
+
for (let i=0; i < args.length; i++) {
|
|
335
|
+
fullStr += strs[i];
|
|
336
|
+
let arg = args[i];
|
|
337
|
+
if (arg && arg.nodeType) {
|
|
338
|
+
fullStr += `<!--${DOMs.length}-->`;
|
|
339
|
+
DOMs.push(arg);
|
|
340
|
+
} else {
|
|
341
|
+
fullStr += arg;
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
fullStr += strs[strs.length - 1];
|
|
345
|
+
|
|
346
|
+
const template = document.createElement('template');
|
|
347
|
+
template.innerHTML = fullStr;
|
|
348
|
+
const content = template.content;
|
|
349
|
+
|
|
350
|
+
// для создания вложенности html элементов заменяем плейсхолдеры <!--${DOMs.length}-->
|
|
351
|
+
const it = document.createTreeWalker(
|
|
352
|
+
content,
|
|
353
|
+
NodeFilter.SHOW_COMMENT
|
|
354
|
+
);
|
|
355
|
+
let node, i = 0;
|
|
356
|
+
for (; node = it.nextNode(); )
|
|
357
|
+
node.replaceWith(DOMs[i++]);
|
|
358
|
+
|
|
359
|
+
if (content.children.length === 1)
|
|
349
360
|
return content.firstChild;
|
|
350
361
|
return content;
|
|
351
362
|
},
|
|
352
|
-
|
|
363
|
+
pipe(data, ...fns) {
|
|
364
|
+
/*
|
|
365
|
+
* КАСТОМНЫЙ PIPE ОПЕРАТОР
|
|
366
|
+
*
|
|
367
|
+
* Никакой магии, обычный синхронный |>
|
|
368
|
+
* для мутации таблиц будет самое то
|
|
369
|
+
*/
|
|
370
|
+
for (const fn of fns)
|
|
371
|
+
data = fn(data);
|
|
372
|
+
return data;
|
|
373
|
+
},
|
|
374
|
+
async pipeAsync(data, ...fns) {
|
|
375
|
+
/*
|
|
376
|
+
* КАСТОМНЫЙ PIPE ОПЕРАТОР 2
|
|
377
|
+
*
|
|
378
|
+
* Никакой магии, обычный асинхронный |>
|
|
379
|
+
* для получения и мутации данных сойдёт
|
|
380
|
+
*/
|
|
381
|
+
for (const fn of fns) {
|
|
382
|
+
let waiter = await data;
|
|
383
|
+
data = await fn(waiter);
|
|
384
|
+
}
|
|
385
|
+
return data;
|
|
386
|
+
},
|
|
387
|
+
form: {
|
|
353
388
|
/*
|
|
354
389
|
* АВТОСОХРАНЕНИЕ ФОРМ
|
|
355
390
|
*
|
|
356
391
|
* Позволяет сохранять состояние формы на случай
|
|
357
392
|
* Если в офисе внезапно выключат свет
|
|
358
393
|
*
|
|
359
|
-
*
|
|
360
|
-
*
|
|
394
|
+
* ???: может сделать более полноценный модуль форм
|
|
395
|
+
* с встроенной валидацией, или чем нибуть ещё
|
|
361
396
|
*/
|
|
362
|
-
|
|
363
|
-
let
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
data[
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
cb(data);
|
|
373
|
-
};
|
|
374
|
-
if(delay<1)return save();
|
|
375
|
-
_.$.on(form,'input',()=>{
|
|
376
|
-
clearTimeout(t);
|
|
377
|
-
t=setTimeout(save,delay);
|
|
397
|
+
read(form) {
|
|
398
|
+
let data = {};
|
|
399
|
+
new FormData(form).forEach((value, key)=>{
|
|
400
|
+
if (data[key] !== undefined) {
|
|
401
|
+
if (!Array.isArray(data[key]))
|
|
402
|
+
data[key] = [data[key]];
|
|
403
|
+
else
|
|
404
|
+
data[key].push(value);
|
|
405
|
+
} else
|
|
406
|
+
data[key] = value;
|
|
378
407
|
});
|
|
408
|
+
return data;
|
|
379
409
|
},
|
|
380
|
-
write(form,data){
|
|
381
|
-
Object.entries(data).forEach(([
|
|
382
|
-
let el=form.elements[
|
|
383
|
-
if (!el)
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
410
|
+
write(form, data) {
|
|
411
|
+
Object.entries(data).forEach(([key,value])=>{
|
|
412
|
+
let el = form.elements[key];
|
|
413
|
+
if (!el)
|
|
414
|
+
return;
|
|
415
|
+
if (el.length)
|
|
416
|
+
[...el].forEach((opt,i)=>{
|
|
417
|
+
let isCheckBox = 'selected';
|
|
418
|
+
if (['checkbox','radio'].includes(opt.type))
|
|
419
|
+
isCheckBox = 'checked';
|
|
420
|
+
|
|
421
|
+
let select = false;
|
|
422
|
+
if (Array.isArray(value)) {
|
|
423
|
+
if (value.includes(opt.value))
|
|
424
|
+
select = true;
|
|
425
|
+
} else if (opt.value == value)
|
|
426
|
+
select = true;
|
|
427
|
+
|
|
428
|
+
opt[isCheckBox] = select;
|
|
429
|
+
});
|
|
394
430
|
else
|
|
395
|
-
el.value=
|
|
431
|
+
el.value = value;
|
|
396
432
|
});
|
|
397
433
|
return data;
|
|
398
434
|
},
|
|
399
435
|
},
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
436
|
+
tables(name, columns, raw, rowKey = 'ID', selection = false) {
|
|
437
|
+
/*
|
|
438
|
+
* МОДУЛЬ АВТОТАБЛИЦ
|
|
439
|
+
*
|
|
440
|
+
* Позволяет быстро генерировать таблицы с особыми свойствами
|
|
441
|
+
*
|
|
442
|
+
* !!!: в columns параметр mutate работает как парсер значения
|
|
443
|
+
* !!!: сортируйте сами путём мутации data, javascript как никак умеет
|
|
444
|
+
* или вообще сортируйте на сервере
|
|
445
|
+
*
|
|
446
|
+
* ???: рассмотреть переделку апи т.к. в текущей реализации гибкость слишком низкая
|
|
447
|
+
*/
|
|
448
|
+
if (!Array.isArray(raw))
|
|
449
|
+
raw = Object.values(raw);
|
|
450
|
+
let state = {
|
|
451
|
+
name: name,
|
|
452
|
+
columns: columns,
|
|
453
|
+
raw: raw,
|
|
454
|
+
rowKey: rowKey,
|
|
455
|
+
data: raw,
|
|
456
|
+
selected: new Set(),
|
|
457
|
+
elem: null,
|
|
458
|
+
|
|
459
|
+
build(elem) {
|
|
460
|
+
this.elem = elem;
|
|
461
|
+
this.render();
|
|
462
|
+
return this;
|
|
463
|
+
},
|
|
464
|
+
|
|
465
|
+
getSelected() {
|
|
466
|
+
return [ ...this.selected ];
|
|
467
|
+
},
|
|
468
|
+
|
|
469
|
+
render() {
|
|
470
|
+
if (!this.elem) return;
|
|
471
|
+
let html = '';
|
|
472
|
+
html += `<thead><tr>`;
|
|
473
|
+
if (selection)
|
|
474
|
+
html += `<th></th>`;
|
|
475
|
+
for (let c of this.columns)
|
|
476
|
+
html += `<th>${c.title || c.key}</th>`;
|
|
477
|
+
html += `</tr></thead>`;
|
|
478
|
+
|
|
479
|
+
html += `<tbody>`;
|
|
480
|
+
for (let row of this.data) {
|
|
481
|
+
let id = row[this.rowKey];
|
|
482
|
+
let sel = this.selected.has(id) ? `selected` : ``;
|
|
483
|
+
|
|
484
|
+
html += `<tr data-id="${id}" class="${sel}">`;
|
|
485
|
+
if (selection)
|
|
486
|
+
html +=
|
|
487
|
+
`<td><input type=checkbox name="${this.name}" value="${id}" /></td>`;
|
|
488
|
+
for (let c of this.columns) {
|
|
489
|
+
let v = row[c.key];
|
|
490
|
+
if (c.mutate)
|
|
491
|
+
v = c.mutate(row);
|
|
492
|
+
html += `<td>${v ?? ``}</td>`;
|
|
493
|
+
}
|
|
494
|
+
html += `</tr>`;
|
|
495
|
+
}
|
|
496
|
+
html += `</tbody>`;
|
|
497
|
+
|
|
498
|
+
this.elem.innerHTML = `<table>${html}</table>`;
|
|
499
|
+
this.elem.firstChild.addEventListener('change', e => {
|
|
500
|
+
let targ = e.target;
|
|
501
|
+
if (targ.type !== 'checkbox' || targ.name !== this.name)
|
|
502
|
+
return;
|
|
503
|
+
|
|
504
|
+
let id = targ.value;
|
|
505
|
+
|
|
506
|
+
if (targ.checked)
|
|
507
|
+
this.selected.add(id);
|
|
508
|
+
else
|
|
509
|
+
this.selected.delete(id);
|
|
510
|
+
})
|
|
511
|
+
}
|
|
512
|
+
};
|
|
513
|
+
return state;
|
|
514
|
+
},
|
|
515
|
+
storage: class {
|
|
516
|
+
constructor(storage, name) {
|
|
517
|
+
this._ = storage;
|
|
518
|
+
this.n = name;
|
|
404
519
|
}
|
|
405
|
-
get=
|
|
406
|
-
set=(key,value)=>this._.setItem(this.n+key,value);
|
|
407
|
-
remove=
|
|
408
|
-
clear=()=>Object.keys(this._)
|
|
409
|
-
.filter(k=>k.startsWith(this.n))
|
|
410
|
-
|
|
520
|
+
get = key=> this._.getItem(this.n + key);
|
|
521
|
+
set = (key, value)=>this._.setItem(this.n + key, value);
|
|
522
|
+
remove = key=> this._.removeItem(this.n + key);
|
|
523
|
+
clear = ()=>Object.keys(this._)
|
|
524
|
+
.filter(k => k.startsWith(this.n))
|
|
525
|
+
.forEach(k => this._.removeItem(k));
|
|
411
526
|
},
|
|
412
|
-
|
|
527
|
+
err: {
|
|
528
|
+
init() {
|
|
529
|
+
window.addEventListener('error',_.err.handleGlobal);
|
|
530
|
+
window.addEventListener('unhandledrejection',_.err.handleRejection);
|
|
531
|
+
},
|
|
532
|
+
print: ()=>{},
|
|
533
|
+
|
|
534
|
+
errors: {},
|
|
535
|
+
_c: 0,
|
|
536
|
+
log(err) {
|
|
537
|
+
_.err.print(_.err._c,err);
|
|
538
|
+
_.err._c++;
|
|
539
|
+
_.err.errors[_.err._c]=err;
|
|
540
|
+
},
|
|
541
|
+
handleGlobal(message,source,line,column,error){
|
|
542
|
+
console.error(message,source+':'+line+':'+column,error)
|
|
543
|
+
_.err.log(message + `\n IN ${source} ON LINE ${line} IN COLUMN ${column}`);
|
|
544
|
+
},
|
|
545
|
+
handleRejection(e){
|
|
546
|
+
const err = e.reason || e;
|
|
547
|
+
console.error(err);
|
|
548
|
+
_.err.log(
|
|
549
|
+
`PROMISE ERROR\n`+
|
|
550
|
+
`${e.stack || e}`
|
|
551
|
+
);
|
|
552
|
+
},
|
|
553
|
+
},
|
|
554
|
+
hotkeys: {
|
|
413
555
|
/*
|
|
414
556
|
* ГОРЯЧИЕ КЛАВИШЫ
|
|
415
557
|
*
|
|
@@ -425,78 +567,126 @@ hotkeys:{
|
|
|
425
567
|
* Вы же не хотите чтобы у вас тормозил поток с 100+ хоткеями
|
|
426
568
|
* Из-за простого печатанья?
|
|
427
569
|
*/
|
|
428
|
-
keys:
|
|
429
|
-
_holds:new Set(),
|
|
430
|
-
_:false,
|
|
570
|
+
keys: new Map(),
|
|
571
|
+
_holds: new Set(),
|
|
572
|
+
_: false,
|
|
431
573
|
|
|
432
|
-
_parse:combo=>combo.split('+').map(k=>k.trim()),
|
|
574
|
+
_parse: combo => combo.split('+').map(k=>k.trim()),
|
|
433
575
|
_match(keys) {
|
|
434
576
|
// Нужно сверять все клавишы, это же КОМБИНАЦИЯ а не отдельные куски
|
|
435
577
|
for (let k of keys) if (!this._holds.has(k)) return false;
|
|
436
578
|
return true;
|
|
437
579
|
},
|
|
438
580
|
_init() {
|
|
439
|
-
if (this._)
|
|
440
|
-
|
|
581
|
+
if (this._)
|
|
582
|
+
return;
|
|
583
|
+
document.addEventListener('keydown', e=>{
|
|
441
584
|
this._holds.add(e.code);// key зависит от раскладки (на Qwerty 'KeyZ' — это 'z', на Йцукен — 'я')
|
|
442
585
|
// code даёт физическое положение клавиши, что важно для игр и хоткеев, и в целом универсальнее
|
|
443
586
|
|
|
444
|
-
for (let
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
h.press(e);
|
|
587
|
+
for (let hotkey of this.keys.values()) {
|
|
588
|
+
if (!this._match(hotkey.keys))
|
|
589
|
+
continue;
|
|
590
|
+
if (hotkey.press && !hotkey.active) {
|
|
591
|
+
hotkey.active = true; // active защищает от множественных срабатываний
|
|
592
|
+
hotkey.press(e);
|
|
451
593
|
}
|
|
452
594
|
}
|
|
453
595
|
});
|
|
454
|
-
|
|
596
|
+
document.addEventListener('keyup', e=>{
|
|
455
597
|
this._holds.delete(e.code);
|
|
456
598
|
|
|
457
|
-
for (let
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
h.release(e);
|
|
599
|
+
for (let hotkey of this.keys.values()) {
|
|
600
|
+
if (hotkey.active && !this._match(hotkey.keys)) {
|
|
601
|
+
hotkey.active=false;
|
|
602
|
+
hotkey.release(e);
|
|
462
603
|
}
|
|
463
604
|
}
|
|
464
605
|
});
|
|
465
|
-
|
|
606
|
+
window.addEventListener('blur', e=>{
|
|
466
607
|
/*
|
|
467
608
|
* При переключении в другое окно автоматического keyup не будет
|
|
468
609
|
* Поэтому сбрасываем всё принудительно, мало ли
|
|
469
610
|
*/
|
|
470
|
-
for (let
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
h.release();
|
|
611
|
+
for (let hotkey of this.keys.values()) {
|
|
612
|
+
if (hotkey.active) {
|
|
613
|
+
hotkey.active = false;
|
|
614
|
+
hotkey.release();
|
|
475
615
|
}
|
|
476
616
|
}
|
|
477
617
|
this._holds.clear();
|
|
478
618
|
});
|
|
479
619
|
this._=true;
|
|
480
620
|
},
|
|
481
|
-
on(combo,press,release) {
|
|
621
|
+
on(combo, press, release) {
|
|
482
622
|
this._init();
|
|
483
|
-
let keys=this._parse(combo);
|
|
623
|
+
let keys = this._parse(combo);
|
|
484
624
|
|
|
485
|
-
this.keys
|
|
625
|
+
this.keys.set(combo, {
|
|
486
626
|
keys,
|
|
487
627
|
// press/releace по умолчанию пустышки для сокращения синаксиса
|
|
488
628
|
press: press || (()=>{}),
|
|
489
629
|
release: release || (()=>{}),
|
|
490
630
|
active: false
|
|
491
|
-
};
|
|
631
|
+
});
|
|
492
632
|
|
|
493
633
|
return this;
|
|
494
634
|
},
|
|
495
635
|
off(combo) {
|
|
496
|
-
|
|
636
|
+
this.keys.delete(combo);
|
|
497
637
|
return this;
|
|
498
638
|
},
|
|
499
639
|
},
|
|
640
|
+
drag: {
|
|
641
|
+
_i: false,
|
|
642
|
+
active: new Map(),
|
|
643
|
+
prevent: e=>e.preventDefault(),
|
|
644
|
+
init(dragger, mover, onStart, onStop) {
|
|
645
|
+
let start=e=>{
|
|
646
|
+
// Проверяем куда нажали, если бы мы не проверяли,
|
|
647
|
+
// То драггер не дал бы нам нажать на кнопки или изменить имя окна
|
|
648
|
+
if (e.target.closest('button,input')) return;
|
|
649
|
+
|
|
650
|
+
this.prevent(e);
|
|
651
|
+
|
|
652
|
+
this.active.set(e.pointerId,{
|
|
653
|
+
x:e.clientX,
|
|
654
|
+
y:e.clientY,
|
|
655
|
+
mover:mover,
|
|
656
|
+
onStop:onStop
|
|
657
|
+
});
|
|
658
|
+
|
|
659
|
+
onStart?.(e);
|
|
660
|
+
};
|
|
661
|
+
if (!this._i) {
|
|
662
|
+
document.addEventListener("pointermove", (e) => this.move(e));
|
|
663
|
+
document.addEventListener("pointerup", (e) => this.stop(e));
|
|
664
|
+
document.addEventListener("pointercancel", (e) => this.stop(e));
|
|
665
|
+
this._i = true;
|
|
666
|
+
}
|
|
667
|
+
dragger.onpointerdown=start;
|
|
668
|
+
dragger.ontouchmove=this.prevent;
|
|
669
|
+
},
|
|
670
|
+
move(e) {
|
|
671
|
+
let p=this.active.get(e.pointerId);
|
|
672
|
+
if(!p) return;
|
|
673
|
+
this.prevent(e);
|
|
674
|
+
|
|
675
|
+
let dx=p.x - e.clientX,
|
|
676
|
+
dy=p.y - e.clientY;
|
|
677
|
+
|
|
678
|
+
p.x=e.clientX;
|
|
679
|
+
p.y=e.clientY;
|
|
680
|
+
|
|
681
|
+
let mov = p.mover;
|
|
682
|
+
mov.style.top=(mov.offsetTop - dy)+"px";
|
|
683
|
+
mov.style.left=(mov.offsetLeft - dx)+"px";
|
|
684
|
+
},
|
|
685
|
+
stop(e) {
|
|
686
|
+
this.active.get(e.pointerId)?.onStop?.(e);
|
|
687
|
+
this.active.delete(e.pointerId);
|
|
688
|
+
},
|
|
689
|
+
},
|
|
500
690
|
win:{
|
|
501
691
|
/*
|
|
502
692
|
* МОДУЛЬ ОКОН
|
|
@@ -524,6 +714,7 @@ win:{
|
|
|
524
714
|
*/
|
|
525
715
|
manager:false,
|
|
526
716
|
hider:false,
|
|
717
|
+
text:'',
|
|
527
718
|
|
|
528
719
|
winAttrs:'',
|
|
529
720
|
dragAttrs:'',
|
|
@@ -552,63 +743,25 @@ win:{
|
|
|
552
743
|
// !!!: в теории можно задать любой айди
|
|
553
744
|
// ???: проверить при скольки окнах генератор начинает тормозить
|
|
554
745
|
do id=Math.random().toString(36).substring(2,8);
|
|
555
|
-
while (_.wins
|
|
746
|
+
while (_.wins.has(id));
|
|
556
747
|
return id;
|
|
557
748
|
},
|
|
558
749
|
_winBtn(win,text,func){
|
|
559
750
|
let b=_.html`<button ${this.btnAttrs}>${text}</button>`;
|
|
560
|
-
|
|
751
|
+
b.addEventListener('click',()=>func(win));
|
|
561
752
|
return b;
|
|
562
753
|
},
|
|
563
754
|
_hiderBtn(win){
|
|
564
|
-
let title=win.langs!== false ? _.lang.win
|
|
755
|
+
let title=win.langs!== false ? _.lang.winTitle(_.win.text+win.langs) : `>${win.name}<`,
|
|
565
756
|
b=_.html`<button id=hider${win.id} ${this.hiderAttrs}${title}/button>`;
|
|
566
|
-
|
|
757
|
+
b.addEventListener('click',()=>this.show(win));
|
|
567
758
|
return b;
|
|
568
759
|
},
|
|
569
|
-
_initWin
|
|
570
|
-
|
|
571
|
-
wEl=win.elem,
|
|
572
|
-
x1=0,y1=0,x2=0,y2=0,
|
|
573
|
-
prevent=e=>e.preventDefault(),
|
|
574
|
-
startW=e=>{
|
|
575
|
-
let targ=e.target;
|
|
576
|
-
// Проверяем куда нажали, если бы мы не проверяли,
|
|
577
|
-
// То драггер не дал бы нам нажать на кнопки или изменить имя окна
|
|
578
|
-
if (['BUTTON','INPUT'].includes(targ.tagName) || targ.closest('button,input')){
|
|
579
|
-
return;
|
|
580
|
-
}
|
|
581
|
-
this.manager.appendChild(wEl);
|
|
582
|
-
|
|
583
|
-
prevent(e);
|
|
584
|
-
x2=e.clientX;
|
|
585
|
-
y2=e.clientY;
|
|
586
|
-
|
|
587
|
-
D.onpointermove=moveW;
|
|
588
|
-
|
|
589
|
-
D.onpointerup=D.onpointercancel=stopW;
|
|
590
|
-
},
|
|
591
|
-
moveW=e=>{
|
|
592
|
-
prevent(e);
|
|
593
|
-
let cX=e.clientX,
|
|
594
|
-
cY=e.clientY;
|
|
595
|
-
|
|
596
|
-
x1=x2 - cX;
|
|
597
|
-
y1=y2 - cY;
|
|
598
|
-
x2=cX;
|
|
599
|
-
y2=cY;
|
|
600
|
-
|
|
601
|
-
wEl.style.top=(wEl.offsetTop - y1) + "px";
|
|
602
|
-
wEl.style.left=(wEl.offsetLeft - x1) + "px";
|
|
603
|
-
},
|
|
604
|
-
stopW=()=>['move','up','cancel'].map(e=>D['onpointer'+e]=null),
|
|
605
|
-
dr=win.drag;
|
|
606
|
-
dr.onpointerdown=startW;
|
|
607
|
-
dr.ontouchmove=prevent;
|
|
608
|
-
},
|
|
760
|
+
_initWin: winState=>
|
|
761
|
+
_.drag.init(winState.drag, winState.elem, ()=>_.win.manager.appendChild(winState.elem)),
|
|
609
762
|
open(name,content='',customAttrs=''){
|
|
610
763
|
let winId=this._ID(),
|
|
611
|
-
winState=
|
|
764
|
+
winState={
|
|
612
765
|
id:winId,
|
|
613
766
|
name:name,
|
|
614
767
|
langs:name,
|
|
@@ -625,79 +778,82 @@ win:{
|
|
|
625
778
|
};
|
|
626
779
|
return this._opn(winState,content);
|
|
627
780
|
},
|
|
628
|
-
_opn(
|
|
781
|
+
_opn(winState,content=''){
|
|
629
782
|
if (!this.manager || !this.hider) throw new Error('Window managers not inited');
|
|
630
783
|
|
|
631
|
-
let wId=
|
|
784
|
+
let wId=winState.id,
|
|
632
785
|
html=
|
|
633
|
-
_.html`<div id=${wId} ${this.winAttrs} ${
|
|
786
|
+
_.html`<div id=${wId} ${this.winAttrs} ${winState.attrs}>
|
|
634
787
|
<div style="display:flex;justify-content:space-between;align-items:center"
|
|
635
788
|
${this.dragAttrs} id=DRAGGER${wId}>
|
|
636
|
-
<span ${this.titleAttrs} id=title${wId}${_.lang.win
|
|
789
|
+
<span ${this.titleAttrs} id=title${wId}${_.lang.winTitle(_.win.text+winState.name)}/span>
|
|
637
790
|
<div id=btns${wId}></div>
|
|
638
791
|
</div>
|
|
639
792
|
<div id=content${wId} style=overflow:auto;width:100%;height:100%>
|
|
640
793
|
${content.replace(/\{winId\}/g,wId)}
|
|
641
794
|
</div>
|
|
642
795
|
</div>`,
|
|
643
|
-
btns=
|
|
644
|
-
for(let b of this.defBtns) btns.append(this._winBtn(
|
|
796
|
+
btns=html.querySelector(`#btns${wId}`);
|
|
797
|
+
for(let b of this.defBtns) btns.append(this._winBtn(winState,...b));
|
|
645
798
|
html.style.overflow='hidden';
|
|
646
799
|
html.style.resize='both';
|
|
647
800
|
|
|
648
801
|
let anim=this.animOpen;
|
|
649
802
|
if (anim)
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
803
|
+
html.addEventListener('animationend',()=>html.classList.remove(anim),this._ae);
|
|
804
|
+
winState.setTitle=nT=>_.win.setTitle(winState,nT);
|
|
805
|
+
winState.toggleFull=e=>_.win.toggleFull(winState);
|
|
806
|
+
winState.close=e=>_.win.close(winState);
|
|
807
|
+
winState.hide=e=>_.win.hide(winState);
|
|
808
|
+
winState.show=e=>_.win.show(winState);
|
|
656
809
|
this.manager.append(html);
|
|
657
810
|
|
|
658
|
-
let win=
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
811
|
+
let win=winState.elem=document.getElementById(wId),
|
|
812
|
+
contentRect=document.getElementById('content'+wId).getBoundingClientRect(),
|
|
813
|
+
windowRect=win.getBoundingClientRect(),
|
|
814
|
+
padX=windowRect.width - contentRect.width,padY=windowRect.height - contentRect.height;
|
|
815
|
+
winState.drag=document.getElementById('DRAGGER'+wId);
|
|
816
|
+
winState.content=document.getElementById('content'+wId);
|
|
663
817
|
|
|
664
|
-
if (
|
|
818
|
+
if (winState.onUnfull.width === 0) {
|
|
665
819
|
// Здесь и задаются координаты...
|
|
666
820
|
// Мастера клин кода не выносите мне мозги прошу
|
|
667
821
|
// Оно же работает!!!
|
|
668
|
-
if (!
|
|
822
|
+
if (!winState.attrs.includes('top')) {
|
|
669
823
|
win.style.top=win.offsetTop - (win.offsetHeight / 2) + 'px';
|
|
670
824
|
win.style.left=win.offsetLeft - (win.offsetWidth / 2) + 'px';
|
|
671
825
|
}
|
|
672
|
-
if (!
|
|
673
|
-
if (!
|
|
826
|
+
if (!winState.attrs.includes('width')) win.style.height=(win.offsetHeight - padX) + 'px';
|
|
827
|
+
if (!winState.attrs.includes('height')) win.style.width=(win.offsetWidth - padY) + 'px';
|
|
674
828
|
} else
|
|
675
|
-
for (let pos in
|
|
676
|
-
win.style[pos] =
|
|
829
|
+
for (let pos in winState.onUnfull)
|
|
830
|
+
win.style[pos] = winState.onUnfull[pos] + 'px'
|
|
677
831
|
|
|
678
|
-
this._initWin(
|
|
679
|
-
|
|
832
|
+
//this._initWin(winState);
|
|
833
|
+
this._initWin(winState);
|
|
834
|
+
winState.drag.addEventListener('contextmenu',(e)=>{
|
|
680
835
|
e.preventDefault();
|
|
681
836
|
if(e.target.closest('button')) return;
|
|
682
|
-
let wT=
|
|
683
|
-
if (!
|
|
837
|
+
let wT=document.getElementById('title'+wId);
|
|
838
|
+
if (!winState.inRename){
|
|
684
839
|
wT.innerHTML=`<input ${this.renameAttrs} id=rename${wId} value="${wT.textContent}">`;
|
|
685
|
-
|
|
840
|
+
winState.inRename=true;
|
|
686
841
|
}else{
|
|
687
|
-
this.setTitle(
|
|
688
|
-
|
|
842
|
+
this.setTitle(winState,document.getElementById('rename'+wId).value);
|
|
843
|
+
winState.inRename=false;
|
|
689
844
|
}
|
|
690
845
|
});
|
|
691
846
|
|
|
692
|
-
if (
|
|
847
|
+
if (winState.state === 'hidened') winState.hide();
|
|
693
848
|
|
|
694
|
-
|
|
849
|
+
_.wins.set(winState.id, winState);
|
|
850
|
+
return winState;
|
|
695
851
|
},
|
|
696
|
-
setTitle(
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
let t=
|
|
700
|
-
h=
|
|
852
|
+
setTitle(winState,newT){
|
|
853
|
+
winState.langs=false;
|
|
854
|
+
winState.name=newT;
|
|
855
|
+
let t=document.getElementById('title'+winState.id),
|
|
856
|
+
h=document.getElementById('hider'+winState.id);
|
|
701
857
|
t.innerHTML=newT;
|
|
702
858
|
t.removeAttribute('data-trans');
|
|
703
859
|
if (h){
|
|
@@ -705,19 +861,19 @@ win:{
|
|
|
705
861
|
h.removeAttribute('data-trans');
|
|
706
862
|
}
|
|
707
863
|
},
|
|
708
|
-
toggleFull(
|
|
709
|
-
let wEl=
|
|
864
|
+
toggleFull(winState){
|
|
865
|
+
let wEl=winState.elem,
|
|
710
866
|
ws=wEl.style,
|
|
711
867
|
wc=wEl.classList,
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
padX=
|
|
715
|
-
padY=
|
|
868
|
+
contentRect=document.getElementById('content'+winState.id).getBoundingClientRect(),
|
|
869
|
+
windowRect=wEl.getBoundingClientRect(),
|
|
870
|
+
padX=windowRect.width - contentRect.width,
|
|
871
|
+
padY=windowRect.height - contentRect.height,
|
|
716
872
|
aOn=this.animFullOn,
|
|
717
873
|
aOff=this.animFullOff,
|
|
718
874
|
fd={
|
|
719
|
-
top:
|
|
720
|
-
width:
|
|
875
|
+
top: windowRect.top, left: windowRect.left,
|
|
876
|
+
width: contentRect.width, height: contentRect.height,
|
|
721
877
|
},
|
|
722
878
|
unful=()=>{
|
|
723
879
|
ws.top=old.top + 'px';
|
|
@@ -727,103 +883,118 @@ win:{
|
|
|
727
883
|
},
|
|
728
884
|
doFul=()=>{
|
|
729
885
|
if (aOn) wc.remove(aOn);
|
|
730
|
-
|
|
731
|
-
|
|
886
|
+
winState.full=true;
|
|
887
|
+
winState.onUnfull=fd;
|
|
732
888
|
ws.top=0;
|
|
733
889
|
ws.left=0;
|
|
734
890
|
ws.width=`calc(100% - ${padX}px)`;
|
|
735
891
|
ws.height=`calc(100% - ${padY}px)`;
|
|
736
|
-
|
|
892
|
+
winState.drag.onpointerdown=null;
|
|
737
893
|
},
|
|
738
894
|
doUnful=()=>{
|
|
739
895
|
if (aOff) wc.remove(aOff);
|
|
740
896
|
unful();
|
|
741
|
-
|
|
742
|
-
this._initWin(
|
|
897
|
+
winState.full=false;
|
|
898
|
+
this._initWin(winState);
|
|
743
899
|
},
|
|
744
|
-
old=
|
|
745
|
-
if (!
|
|
900
|
+
old=winState.onUnfull;
|
|
901
|
+
if (!winState.full) {
|
|
746
902
|
if (aOn) {
|
|
747
903
|
wc.add(aOn);
|
|
748
|
-
|
|
904
|
+
wEl.addEventListener('animationend',doFul,this._ae);
|
|
749
905
|
}else doFul();
|
|
750
906
|
} else {
|
|
751
907
|
if (aOff) {
|
|
752
908
|
wc.add(aOff);
|
|
753
909
|
unful();
|
|
754
|
-
|
|
910
|
+
wEl.addEventListener('animationend',doUnful,this._ae);
|
|
755
911
|
}else doUnful();
|
|
756
912
|
}
|
|
757
913
|
},
|
|
758
|
-
close(
|
|
759
|
-
let w=
|
|
914
|
+
close(winState){
|
|
915
|
+
let w=winState.elem,
|
|
760
916
|
remover=()=>{
|
|
761
|
-
let dr=
|
|
917
|
+
let dr=winState.drag,D=document;
|
|
762
918
|
dr.onpointerdown=dr.ontouchmove=null;
|
|
763
919
|
// Удаляем обработчики висящие на документе
|
|
764
920
|
// Если их не удалять рано или поздно случится утечка памяти
|
|
765
921
|
// Я не знаю как я жил во времена 2.0 когда движок только появился
|
|
766
922
|
['move','up','cancel'].map(e=>D['onpointer'+e]=null);
|
|
767
923
|
w.remove();
|
|
768
|
-
|
|
924
|
+
_.wins.delete(winState.id);
|
|
769
925
|
};
|
|
770
926
|
if (w.style.display== 'none'){
|
|
771
|
-
|
|
927
|
+
document.getElementById('hider'+winState.id).remove();
|
|
772
928
|
remover();
|
|
773
929
|
}else{
|
|
774
930
|
let anim=this.animClose;
|
|
775
931
|
if(anim){
|
|
776
932
|
w.classList.add(anim);
|
|
777
|
-
|
|
933
|
+
w.addEventListener('animationend',remover,this._ae);
|
|
778
934
|
}else
|
|
779
935
|
remover();
|
|
780
936
|
}
|
|
781
937
|
},
|
|
782
|
-
hide(
|
|
783
|
-
let wEl=
|
|
938
|
+
hide(winState){
|
|
939
|
+
let wEl=winState.elem,
|
|
784
940
|
wc=wEl.classList,
|
|
785
941
|
anim=this.animHide,
|
|
786
942
|
hider=()=>{
|
|
787
943
|
wEl.style.display='none';
|
|
788
944
|
if(anim)wc.remove(anim);
|
|
789
|
-
|
|
790
|
-
this.hider.append(this._hiderBtn(
|
|
945
|
+
winState.state='hidened';
|
|
946
|
+
this.hider.append(this._hiderBtn(winState));
|
|
791
947
|
}
|
|
792
948
|
if(anim){
|
|
793
949
|
wc.add(anim);
|
|
794
|
-
|
|
950
|
+
wEl.addEventListener('animationend',hider,this._ae);
|
|
795
951
|
}else
|
|
796
952
|
hider();
|
|
797
953
|
},
|
|
798
|
-
show(
|
|
799
|
-
let wEl=
|
|
954
|
+
show(winState){
|
|
955
|
+
let wEl=winState.elem,
|
|
800
956
|
wc=wEl.classList,
|
|
801
957
|
anim=this.animShow,
|
|
802
|
-
hider=
|
|
958
|
+
hider=document.getElementById('hider'+winState.id),
|
|
803
959
|
shower=()=>{
|
|
804
960
|
if(anim)wc.remove(anim);
|
|
805
|
-
|
|
961
|
+
winState.state='opened';
|
|
806
962
|
}
|
|
807
963
|
wEl.style.display='';
|
|
808
964
|
hider.remove();
|
|
809
965
|
if(anim){
|
|
810
966
|
wc.add(anim);
|
|
811
|
-
|
|
967
|
+
wEl.addEventListener('animationend',shower,this._ae);
|
|
812
968
|
}else
|
|
813
969
|
shower()
|
|
814
970
|
},
|
|
971
|
+
/*
|
|
972
|
+
* о да, ниже идёт самая крутая фишка которую я готовлю к 2.2
|
|
973
|
+
*
|
|
974
|
+
* СОХРАНЕНИЕ-ВОССТАНОВКА ОКОН
|
|
975
|
+
* Помните автоформы? Здесь я поступил лучше
|
|
976
|
+
* Вы можете полностью сохранить окна, как - решаете вы, но лучше
|
|
977
|
+
* Вместо колбека я теперь просто делаю разовый читатель, так намного гибче
|
|
978
|
+
* Плюсом я делаю разовый восстановитель который возвращает все окна
|
|
979
|
+
* Так тоже в разы гибче, авось у вас в окнах были вебсокеты и их нужно восстановить
|
|
980
|
+
* Проще записать результат а потом прогнать проверку по data-ws атрибутам
|
|
981
|
+
* Или как вы ещё придумаете
|
|
982
|
+
*
|
|
983
|
+
* !!!: Оно работает настолько гибко что в теории можно сделать виртуальные рабочие столы
|
|
984
|
+
*/
|
|
815
985
|
read(){
|
|
816
986
|
let store = {};
|
|
817
|
-
for (let winId
|
|
818
|
-
let win={...
|
|
987
|
+
for (let [winId, winPre] of _.wins) {
|
|
988
|
+
let win = { ...winPre },
|
|
819
989
|
size=win.onUnfull,
|
|
820
990
|
wEl = win.elem,
|
|
821
|
-
|
|
991
|
+
contentRect=win.content.getBoundingClientRect(),
|
|
992
|
+
windowRect=wEl.getBoundingClientRect();
|
|
822
993
|
win.realContent=win.content.innerHTML;
|
|
823
|
-
size.top=
|
|
824
|
-
size.left=
|
|
825
|
-
size.height=wEl.offsetHeight - (
|
|
826
|
-
size.width=wEl.offsetWidth - (
|
|
994
|
+
size.top=windowRect.top;
|
|
995
|
+
size.left=windowRect.left;
|
|
996
|
+
size.height=wEl.offsetHeight - (windowRect.height - contentRect.height);
|
|
997
|
+
size.width=wEl.offsetWidth - (windowRect.width - contentRect.width);
|
|
827
998
|
delete win.elem;
|
|
828
999
|
delete win.drag;
|
|
829
1000
|
delete win.content;
|
|
@@ -836,15 +1007,16 @@ win:{
|
|
|
836
1007
|
let win=state[winId],
|
|
837
1008
|
content=win.realContent;
|
|
838
1009
|
delete win.realContent;
|
|
839
|
-
_.wins
|
|
1010
|
+
_.wins.set(winId, win);
|
|
840
1011
|
this._opn(win,content);
|
|
841
1012
|
}
|
|
1013
|
+
return _.wins;
|
|
842
1014
|
},
|
|
843
1015
|
},
|
|
844
|
-
wins:
|
|
1016
|
+
wins: new Map(),
|
|
845
1017
|
};
|
|
846
1018
|
|
|
847
|
-
|
|
1019
|
+
window.addEventListener('popstate',()=>{
|
|
848
1020
|
let l=_.link
|
|
849
1021
|
/*
|
|
850
1022
|
* popstate срабатывает когда:
|
|
@@ -867,4 +1039,4 @@ _.$.on(window,'popstate',()=>{
|
|
|
867
1039
|
l._i=false;
|
|
868
1040
|
});
|
|
869
1041
|
|
|
870
|
-
return _}
|
|
1042
|
+
return _};
|