Commit ef36edad by Иван Кубота

clean out and refactor

parent 72c40ff0
[
{
"id": 1,
"name": "мучное"
},
{
"id": 2,
"name": "мясо"
},
{
"id": 3,
"name": "сыр"
},
{
"id": 4,
"name": "сахар"
},
{
"id": 5,
"name": "варенье"
},
{
"id": 6,
"name": "сладкое"
},
{
"id": 7,
"name": "шоколад"
},
{
"id": 8,
"name": "морепродукты"
},
{
"id": 9,
"name": "овощи"
},
{
"id": 10,
"name": "шампунь"
},
{
"id": 11,
"name": "гигиена"
},
{
"id": 12,
"name": "чай"
},
{
"id": 13,
"name": "крупа"
},
{
"id": 14,
"name": "булгур"
},
{
"id": 15,
"name": "киноа"
},
{
"id": 16,
"name": "рис"
},
{
"id": 17,
"name": "молочный продукт"
},
{
"id": 18,
"name": "молочное"
},
{
"id": 19,
"name": "риет"
},
{
"id": 20,
"name": "харам"
},
{
"id": 21,
"name": "кускус"
},
{
"id": 22,
"name": "напиток"
},
{
"id": 23,
"name": "вода"
},
{
"id": 24,
"name": "паштет"
},
{
"id": 25,
"name": "салат"
},
{
"id": 26,
"name": "рыба"
},
{
"id": 27,
"name": "травы"
},
{
"id": 28,
"name": "канцтовары"
},
{
"id": 29,
"name": "суп"
},
{
"id": 30,
"name": "блины"
},
{
"id": 31,
"name": "ветчина"
},
{
"id": 32,
"name": "свинина"
},
{
"id": 33,
"name": "курица"
},
{
"id": 34,
"name": "яйцо"
},
{
"id": 35,
"name": "малина"
},
{
"id": 36,
"name": "черника"
},
{
"id": 38,
"name": "смородина"
},
{
"id": 39,
"name": "постное"
},
{
"id": 40,
"name": "слива"
},
{
"id": 41,
"name": "сернослив"
},
{
"id": 42,
"name": "свекла"
},
{
"id": 43,
"name": "яблоко"
},
{
"id": 44,
"name": "арахис"
},
{
"id": 45,
"name": "мята"
},
{
"id": 46,
"name": "котлеты"
},
{
"id": 47,
"name": "креветки"
},
{
"id": 48,
"name": "макароны"
},
{
"id": 49,
"name": "капуста"
},
{
"id": 50,
"name": "хлеб"
},
{
"id": 51,
"name": "борщ"
},
{
"id": 52,
"name": "первое"
},
{
"id": 53,
"name": "готовая еда"
},
{
"id": 54,
"name": "кунжут"
},
{
"id": 55,
"name": "кальмар"
},
{
"id": 56,
"name": "лосось"
},
{
"id": 57,
"name": "индейка"
},
{
"id": 58,
"name": "колбаса"
},
{
"id": 59,
"name": "зелень"
},
{
"id": 60,
"name": "зеленый горошек"
},
{
"id": 61,
"name": "кукуруза"
},
{
"id": 62,
"name": "лук"
},
{
"id": 63,
"name": "перец"
},
{
"id": 64,
"name": "сельдерей"
}
]
\ No newline at end of file
This diff is collapsed. Click to expand it.
{"type":"https:\/\/tools.ietf.org\/html\/rfc2616#section-10","title":"An error occurred","detail":"Not Found Quiz","trace":[{"namespace":"","short_class":"","class":"","type":"","function":"","file":"\/home\/forge\/api.local.vkusvill.testin.ru\/src\/Controller\/CreateQuizAttempt.php","line":53,"args":[]},{"namespace":"App\\Controller","short_class":"CreateQuizAttempt","class":"App\\Controller\\CreateQuizAttempt","type":"-\u003E","function":"__invoke","file":"\/home\/forge\/api.local.vkusvill.testin.ru\/vendor\/symfony\/http-kernel\/HttpKernel.php","line":151,"args":[]},{"namespace":"Symfony\\Component\\HttpKernel","short_class":"HttpKernel","class":"Symfony\\Component\\HttpKernel\\HttpKernel","type":"-\u003E","function":"handleRaw","file":"\/home\/forge\/api.local.vkusvill.testin.ru\/vendor\/symfony\/http-kernel\/HttpKernel.php","line":68,"args":[]},{"namespace":"Symfony\\Component\\HttpKernel","short_class":"HttpKernel","class":"Symfony\\Component\\HttpKernel\\HttpKernel","type":"-\u003E","function":"handle","file":"\/home\/forge\/api.local.vkusvill.testin.ru\/vendor\/symfony\/http-kernel\/Kernel.php","line":198,"args":[]},{"namespace":"Symfony\\Component\\HttpKernel","short_class":"Kernel","class":"Symfony\\Component\\HttpKernel\\Kernel","type":"-\u003E","function":"handle","file":"\/home\/forge\/api.local.vkusvill.testin.ru\/public\/index.php","line":32,"args":[]}]}
\ No newline at end of file
cd /home/forge/kus-quizard
node . -r -n
\ No newline at end of file
...@@ -4,7 +4,6 @@ const APP_HOST = env.APP_HOST || "127.0.0.1"; ...@@ -4,7 +4,6 @@ const APP_HOST = env.APP_HOST || "127.0.0.1";
const APP_PORT = env.APP_PORT || 4000; const APP_PORT = env.APP_PORT || 4000;
const DB_PATH = env.DB_PATH || "./db/users.json"; const DB_PATH = env.DB_PATH || "./db/users.json";
const data = require("./db.js");
const App = require('express'); const App = require('express');
const Router = require('node-async-router'); const Router = require('node-async-router');
...@@ -32,7 +31,7 @@ const serveBundle = async function(tpl, res){ ...@@ -32,7 +31,7 @@ const serveBundle = async function(tpl, res){
} }
const cacheEntry = posixName;//+new Date()+'_'+Math.random().toString(36).substr(2) const cacheEntry = posixName;//+new Date()+'_'+Math.random().toString(36).substr(2)
res.end(tpl res.end(tpl
.replace('$DATA$', JSON.stringify(data)) //.replace('$DATA$', JSON.stringify(data))
.replace('$BUNDLE$', .replace('$BUNDLE$',
'<script src="'+cacheEntry+'"></script>') '<script src="'+cacheEntry+'"></script>')
); );
...@@ -99,7 +98,7 @@ var transformServe = function(dir) { ...@@ -99,7 +98,7 @@ var transformServe = function(dir) {
if (req.url.substr(-5) === '.scss') { if (req.url.substr(-5) === '.scss') {
// not secure // not secure
console.log('scss', dir, req.url, __dirname) //console.log('scss', dir, req.url, __dirname)
fs.readFile(path.join(__dirname,dir,req.url), function(err, data){ fs.readFile(path.join(__dirname,dir,req.url), function(err, data){
if( err ){ if( err ){
next(); next();
...@@ -108,7 +107,7 @@ var transformServe = function(dir) { ...@@ -108,7 +107,7 @@ var transformServe = function(dir) {
file: path.join(__dirname,dir,req.url), file: path.join(__dirname,dir,req.url),
sourceMap: true, sourceMap: true,
importer: function(url, prev, done) { importer: function(url, prev, done) {
console.log(url, prev, done) //console.log(url, prev, done)
// url is the path in import as is, which LibSass encountered. // url is the path in import as is, which LibSass encountered.
// prev is the previously resolved path. // prev is the previously resolved path.
// done is an optional callback, either consume it or return value synchronously. // done is an optional callback, either consume it or return value synchronously.
...@@ -122,7 +121,7 @@ var transformServe = function(dir) { ...@@ -122,7 +121,7 @@ var transformServe = function(dir) {
contents: fs.readFileSync(name)+'' contents: fs.readFileSync(name)+''
}); });
console.log({name, __dirname, dir, url, prev}) //console.log({name, __dirname, dir, url, prev})
},10); },10);
// OR // OR
//var result = someSyncFunction(url, prev); //var result = someSyncFunction(url, prev);
...@@ -135,7 +134,7 @@ var transformServe = function(dir) { ...@@ -135,7 +134,7 @@ var transformServe = function(dir) {
} }
res.header('Content-Type', 'text/css'); res.header('Content-Type', 'text/css');
res.end(result.css) res.end(result.css)
debugger //debugger
/*...*/ }); /*...*/ });
} }
}); });
...@@ -143,7 +142,6 @@ var transformServe = function(dir) { ...@@ -143,7 +142,6 @@ var transformServe = function(dir) {
console.log('Serve jsx', dir, req.url) console.log('Serve jsx', dir, req.url)
fs.readFile(path.join(dir, req.url), function(err, data){ fs.readFile(path.join(dir, req.url), function(err, data){
if( err ){ if( err ){
console.log('No jsx', err)
next(); next();
}else{ }else{
...@@ -157,7 +155,6 @@ var transformServe = function(dir) { ...@@ -157,7 +155,6 @@ var transformServe = function(dir) {
cache[ req.url + '.map' ] = JSON.stringify( result.map ); cache[ req.url + '.map' ] = JSON.stringify( result.map );
res.set( 'SourceMap', req.url + '.map' ); res.set( 'SourceMap', req.url + '.map' );
res.end( result.code ); res.end( result.code );
debugger;
} }
}) })
} }
...@@ -172,30 +169,29 @@ var transformServe = function(dir) { ...@@ -172,30 +169,29 @@ var transformServe = function(dir) {
app.use(transformServe('public')); app.use(transformServe('public'));
app.use(transformServe('src')); app.use(transformServe('src'));
var lives = []; var lives = [];
app.use('/', function(req, res, next) { app.use('/', function(req, res, next){
console.log(req.originalUrl) console.log( req.originalUrl )
if(req.originalUrl === '/'){ if( req.originalUrl === '/' ){
fs.readFile(dir(pack.src, pack.entry.html), function(err, data) { fs.readFile( dir( pack.src, pack.entry.html ), function( err, data ){
if(err){ if( err ){
res.end(JSON.stringify(err, null, 2 )); res.end( JSON.stringify( err, null, 2 ) );
}else{ }else{
serveBundle(data+'', res); serveBundle( data + '', res );
} }
}); } );
}else if(req.originalUrl === '/[live]'){ }else if( req.originalUrl === '/[live]' ){
lives.push(res); lives.push( res );
}else if( req.originalUrl in cache ){
res( cache[ req.originalUrl ] );
}else{ }else{
if(req.originalUrl in cache){ next();
res( cache[ req.originalUrl ] );
}else{
next();
}
} }
}); });
app.use(App.static(dir(pack.public))) app.use(App.static(dir(pack.public)));
//app.use(App.static(dir(pack.src)))
app.use(App.static(dir(pack.src)))
...@@ -236,7 +232,7 @@ let debounceUpdate = function(filename){ ...@@ -236,7 +232,7 @@ let debounceUpdate = function(filename){
} }
}; };
var watch = require('recursive-watch'); var watch = require('recursive-watch');
watch('./public', function(filename){ watch('./src', function(filename){
if(filename.indexOf('_tmp_')>-1) if(filename.indexOf('_tmp_')>-1)
return; return;
......
var fs = require('fs')
var xx =require("@babel/core").transform(
fs.readFileSync('./public/js/view/page/export.jsx'),
{
"plugins": [
["@babel/plugin-transform-react-jsx", {
"pragma": "D.h", // default pragma is React.createElement
"pragmaFrag": "D.f", // default is React.Fragment
"throwIfNamespace": false // defaults to true
}]
]
}, function(c,d,e) {
console.log(d.code.replace(/\\u([\d\w]{4})/gi, (m, g) => String.fromCharCode(parseInt(g, 16))))
debugger
});
/*
,
"pragma": "Zrx.h",
"pragmaFrag": "Zrx.Fragment",
"throwIfNamespace": false
*/
\ No newline at end of file
{
"question": "Выберите лишний ингредиент, НЕ входящий в продукт \"Салат винегрет с маринованной капустой\"",
"answers": [
{
"correct": false,
"text": "горошек зелёный консервированный"
},
{
"correct": true,
"text": "шампиньоны"
},
{
"correct": false,
"text": "масло подсолнечное нерафинированно"
},
{
"correct": false,
"text": "капуста маринованная"
},
{
"correct": false,
"text": "огурцы консервированные"
},
{
"correct": false,
"text": "свекла столовая отварная,"
}
],
"log": [
"Random seed: bxa9uo",
"Quiz generate radio „Do not contain“",
"Have >= 2 components: 75",
"Matched product: Салат винегрет с маринованной капустой",
"Have 7 components. Took 5:",
" > огурцы консервированные",
" > горошек зелёный консервированный",
" > масло подсолнечное нерафинированно",
" > капуста маринованная",
" > свекла столовая отварная,",
"\nTags: Салаты и закуски",
"Matched 4 component donors.",
"Nearest candidate have got 1 similar tag.",
"Filtered candidates with [1] similar tags.",
"Use Салат «Грин Энерджи» as donor.",
"Because it has 1 similar tags: Салаты и закуски",
"\n << шампиньоны"
],
"type": "radio",
"image": "",
"category": 1
}
\ No newline at end of file
...@@ -3,8 +3,6 @@ ...@@ -3,8 +3,6 @@
"version": "0.1.0", "version": "0.1.0",
"main": "bin/index.js", "main": "bin/index.js",
"dependencies": { "dependencies": {
"@babel/plugin-transform-modules-commonjs": "^7.8.3",
"csv-parser": "^2.3.2",
"express": "^4.17.1", "express": "^4.17.1",
"node-async-router": "^0.0.2", "node-async-router": "^0.0.2",
"node-sass": "^4.13.1", "node-sass": "^4.13.1",
...@@ -20,6 +18,7 @@ ...@@ -20,6 +18,7 @@
"devDependencies": { "devDependencies": {
"@babel/core": "^7.8.3", "@babel/core": "^7.8.3",
"@babel/plugin-transform-modules-amd": "^7.8.3", "@babel/plugin-transform-modules-amd": "^7.8.3",
"@babel/plugin-transform-modules-commonjs": "^7.8.3",
"@babel/plugin-transform-react-jsx": "^7.8.3" "@babel/plugin-transform-react-jsx": "^7.8.3"
}, },
"plugins": [ "plugins": [
......
;(function(Path) {
'use strict';
var log = console.log;
/*var log = function() {
console.log.apply(console, ['Define'].concat([].slice.call(arguments)));
};*/
var head = document.getElementsByTagName('head')[0];
var cssLoader = function(fileName) {
var link = document.createElement('link');
link.setAttribute('rel', 'stylesheet');
link.setAttribute('type', 'text/css');
link.setAttribute('href', fileName);
head.appendChild(link);
return true;
};
var jsLoader = function(fileName) {
fileName = fileName.replace(/src\//,'');
var script = document.createElement('script');
script.setAttribute('type', script.type = 'text/javascript');
script.onload = function(){
};
script.setAttribute('src', script.src = fileName);
head.appendChild(script);
};
var InstantLoaders = [
{name: '.scss', loader: cssLoader},
{name: '.css', loader: cssLoader},
{name: '.jsx', loader: jsLoader},
{name: '.js', loader: jsLoader},
];
var Ready = function() {}; Ready.prototype = {ready: true};
var Wait = function(fileName) {this.fileName = fileName; this.wait = [];};
Wait.prototype = {
fileName: '',
ready: false,
resolving: 0,
checkInstantLoad: function() {
for( var i = 0, _i = InstantLoaders.length; i < _i; i++ ){
const instantLoader = InstantLoaders[ i ];
if(this.fileName.substr(-instantLoader.name.length).toLowerCase()===instantLoader.name){
if(instantLoader.loader(this.fileName)) {
return true;
}
break;
}
}
return false;
}
};
var wait = {
exports: Ready()
};
var defined = {
};
var resolve = function(base, file) {
return Path.resolve(Path.dirname(base), file);
};
var exec = function(def) {
var exports = {};
def.fn.apply(null, def.deps.map(function(dep) {
if(dep === 'exports'){
return exports;
}
return defined[resolve(def.fileName, dep)];
}));
defined[def.fileName] = exports;
var waiters = wait[def.fileName].wait;
if(wait[def.fileName] instanceof Wait){
wait[ def.fileName ] = new Ready();
for( var i = 0, _i = waiters.length; i < _i; i++ ){
var waitElement = waiters[ i ];
if( waitElement instanceof Wait ){
waitElement.resolving--;
}
}
for( var i = 0, _i = waiters.length; i < _i; i++ ){
var waitElement = waiters[ i ];
if( waitElement.resolving === 0 ){
exec( waitElement.def )
}
}
}
};
window.define = function(fileName, deps, fn) {
fileName = Path.trim(fileName);
defined[fileName] = {fileName:fileName, deps: deps, fn:fn};
deps = deps.map(function(dep) {
return dep === 'exports' ? 'exports': resolve(fileName, dep);
});
var unresolved = deps.filter(function(dep) {
if(dep === 'exports'){
return false;
}
var resolvedName = dep;
var waiter = wait[resolvedName];
if(!wait[resolvedName]){
waiter = wait[resolvedName] = new Wait(resolvedName)
}
if(!waiter.ready){
waiter.wait.push( fileName );
if( waiter.checkInstantLoad() ){
wait[ resolvedName ] = new Ready();
}
}
return !wait[resolvedName].ready;
});
if(!(fileName in wait)){
wait[fileName] = new Wait(fileName);
}
var waiter = wait[fileName];
if(unresolved.length === 0){
log(fileName, 'deps fullfilled');
exec(defined[fileName])
}else{
log(fileName, 'is waiting for', unresolved);
for( var i = 0, _i = unresolved.length; i < _i; i++ ){
var unresolvedElement = unresolved[ i ];
waiter.wait.push(resolve(fileName, unresolvedElement));
waiter.resolving++;
}
}
};
window.define.wait = wait;
window.define.defined = defined;
})(window.__Path);
\ No newline at end of file
function sqlEscape(str) {
return (str+'').replace(/[\0\x08\x09\x1a\n\r"'\\\%]/g, function (char) {
switch (char) {
case "\0":
return "\\0";
case "\x08":
return "\\b";
case "\x09":
return "\\t";
case "\x1a":
return "\\z";
case "\n":
return "\\n";
case "\r":
return "\\r";
case "\"":
case "'":
case "\\":
case "%":
return "\\"+char; // prepends a backslash to backslash, percent,
// and double/single quotes
default:
return char;
}
});
}
const indexKey = {
connections: 'cid',
tags: 'id'//'iID'
},
yamlEscape = function(a) {
return typeof a === 'number'? a : '\''+(a+'').replace(/'/,'\'\'')+'\''
};
const formatters = {
json: function(data) {
return JSON.stringify(data, null, 2);
},
csv: toCSV,
sql: function(data) {
if(data.length){
const item = data[0], header = [];
for(let key in item){
header.push(key);
}
const rows = data.map(item=>header.map(key=>item[key]));
return [ 'INSERT INTO `' + store.get( 'exportTableName' ) + '` (' + header.map( sqlEscape ).map( a => '`' + a + '`' ).join( ', ' ) + ') VALUES' ].concat(
rows.map( row => '(' + row.map( sqlEscape ).map( a => '\'' + a + '\'' ).join( ', ' ) + ')' ).join( ',\n' )
).join( '\n' ) + ';';
}else{
return ';'
}
},
yaml: function(data, exportData) {
const index = indexKey[exportData]
return data
.map(item=>
[exportData.substr(0,exportData.length-1)+item[index]+':'].concat(
Object.keys(item)
.filter(key=>key !== index)
.map(key=>' '+key+': '+ yamlEscape(item[key]))
).join('\n')
).join('\n')
}
};
const exportLogic = function(exportEl) {
store.sub(['exportData', 'exportFormat', 'lastUpdate'], function(exportData, exportFormat) {
var arr = [];
var fullData = dP[exportData];
for(var k in fullData){
arr.push(fullData[k]);
}
exportEl.value = exportFormat in formatters ? formatters[exportFormat](arr, exportData): 'Create formatter '+exportFormat;
});
return exportEl;
};
\ No newline at end of file
let ComponentsOfProduct;
quizTypes.checkbox = [ ComponentsOfProduct = {
probability: 10,
correct: {min: 2, max: 4},
wrong: {min: 1, max: 4},
answers: {min: 3, max: 6},
doNotLogUnique: true,
questionCmpAmount: 2,
type: 'Select components of product',
products: {from: 3, to: 7},
minSimilarTags: 1,
from: qB.prebuild.similarTaggedProducts.fn,
question(income, log){
return `Какие ингредиенты входят в продукт ${lapk(income.correct.title)}`
},
answer(income, log){
const correct = shuffle(income.allUniq)
.slice(0, rand(this.correct.min, this.correct.max))
.map(i=>new Answer.Correct(textFormat(i.name))),
componentsNotInCorrect = qB.getComponentsNotInCorrect(income),
maxWrongs = Math.min(this.wrong.max, this.answers.max-correct.length),
wrongCount = rand(this.wrong.min, maxWrongs),
wrongs = componentsNotInCorrect
.slice(0, wrongCount)
.map(i=>new Answer.Wrong(textFormat(i.name)));
log.push('');
log.push(`Took ${correct.length} correct:`);
correct.forEach(a=>log.push(` > ${a.text}`))
log.push('');
log.push(`And ${wrongs.length} wrong:`);
wrongs.forEach(a=>log.push(` > ${a.text}`))
return shuffle(correct.concat(wrongs));
}
},
Object.assign({}, ComponentsOfProduct, {
probability: 4,
correct: {min: 2, max: 4},
wrong: {min: 2, max: 4},
answers: {min: 4, max: 8},
realMaxAnswers: 6,
question(income, log){
return `Какие ингредиенты НЕ входят в продукт ${lapk(income.correct.title)}`
},
answer(income, log){
log.push('Generate Answers by `Select components of product` subroutine');
const result = ComponentsOfProduct.answer.call(this, income, log);
log.push('Reversing');
const reversed = result
.map(a=>a instanceof Answer.Correct ? new Answer.Wrong(a.text) : new Answer.Correct(a.text));
const right = reversed.filter(a=>a instanceof Answer.Correct),
wrong = reversed.filter(a=>a instanceof Answer.Wrong);
return shuffle(right.concat(wrong.slice(0, Math.min(this.wrong.max, this.realMaxAnswers - right.length))));
}
}),
{
probability: 10,
correct: {min: 2, max: 4},
wrong: {min: 1, max: 3},
answers: {min: 4, max: 6},
doNotLogUnique: true,
questionCmpAmount: 2,
type: 'Select components of product',
products: {from: 2, to: 4},
minSimilarTags: 1,
from(log){
const products = qB.randomProduct({
minComponents: 1,
amount: this.products,
connectedByTags: true,
minSimilarTags: this.minSimilarTags,
doNotTrim: true
}, log),
componentsWithProducts = qB.getComponentsWithSharedProducts(products, 2);
log.push('Shared products count: '+componentsWithProducts.length);
if(componentsWithProducts.length === 0)
return false;
const bounded = componentsWithProducts.filter(cp=>
cp.products.length >= this.correct.min &&
products.length - cp.products.length >= this.wrong.min
);
log.push('After bounding by correct/wrong/full count '+bounded.length+' left');
if(bounded.length === 0){
log.push('Does not meet count criteria');
return false;
}
const chosen = rand(bounded);
log.push('Chosen component: '+chosen.component.name);
return {
baseProduct: chosen.products[0],
component: chosen.component,
correct: chosen.products,
wrong: products.filter(p=>chosen.products.indexOf(p)===-1)
};
},
question(income, log){
return `В какие продукты входит следующий ингредиент ${lapk(textFormat(income.component.name))}`
},
answer(income, log){
const correct = income.correct
.slice(0, rand(this.correct))
.map(p=>new Answer.Correct(p.title)),
wrong = income.wrong
.slice(0, Math.min(rand(this.wrong), this.answers.max - correct.length))
.map(p=>new Answer.Wrong(p.title));
log.push('');
log.push(`Get ${correct.length} correct products:`);
correct.map(c=>log.push(` > ${c.text}`));
log.push('');
log.push(`Get ${correct.length} wrong products:`);
wrong.map(c=>log.push(` > ${c.text}`));
return shuffle(correct.concat(wrong));
}
}
];
\ No newline at end of file
quizTypes.checkboxPhoto = [
{
questionMinComponentsCount: 2,
probability: 8,
type: 'What components are in it',
answers: {min: 3, max: 6},
correct: {min: 2, max: 4},
wrong: {min: 1, max: 4},
from: qB.prebuild.similarTaggedProductWithPhotoAndComponents.fn,
question(income, log){
return 'Какие ингредиенты входят в продукт, изображенный на картинке?'
},
answer( income, log ){
let corrects = income.allCorrect
.slice(0, rand(this.correct))
.map(a=>new Answer.Correct(textFormat(a.name))),
wrongs = income.allWrong
.slice(0, rand(this.wrong))
.map(a=>new Answer.Wrong(textFormat(a.name)));
return shuffle(
corrects
.concat(wrongs)
.slice(
0,
Math.min(
this.answers.max,
corrects.length+wrongs.length)));
}
},
{
questionMinComponentsCount: 2,
probability: 3,
type: 'What components are NOT in it',
answers: {min: 3, max: 6},
correct: {min: 2, max: 4},
wrong: {min: 1, max: 4},
from: qB.prebuild.similarTaggedProductWithPhotoAndComponents.fn,
question(income, log){
return 'Какие ингредиенты НЕ входят в продукт, изображенный на картинке?'
},
answer( income, log ){
let corrects = income.allWrong
.slice(0, rand(this.correct))
.map(a=>new Answer.Correct(textFormat(a.name))),
wrongs = income.allCorrect
.slice(0, rand(this.wrong))
.map(a=>new Answer.Wrong(textFormat(a.name)));
return shuffle(
corrects
.concat(wrongs)
.slice(
0,
Math.min(
this.answers.max,
corrects.length+wrongs.length)));
}
}
];
\ No newline at end of file
const qB = {
randomProduct: function({
minComponents,
amount,
single,
connectedByTags,
minSimilarTags,
doNotTrim,
withPhoto
}, log) {
let filtered = Object.values(dP.products);
filtered = filtered
.filter((p)=>p.getComponents().length >= minComponents);
log.push(`Have >= ${minComponents} components: `+filtered.length);
if(!filtered.length)
return;
if(single){
amount = 1;
}
let minAmount, maxAmount;
if(typeof amount !== 'number'){
minAmount = amount.from;
maxAmount = amount.to;
}else{
minAmount = maxAmount = amount;
}
let similarClusters = [];
if(connectedByTags){
let tagsHash = {};
filtered.forEach(p => {
p.getTags().forEach(({id})=>
(tagsHash[id] || (tagsHash[id] = [])).push(p)
);
});
let similarTags = {};
filtered.forEach(p => {
const tags = p.getTags(),
tagsStr = tags.map(({id})=>id).sort().join(',');
if(tagsStr in similarTags)
return;
let similar = filtered
.map(subP =>
({
similar: subP.getTags().filter(subT => tags.indexOf(subT)>-1).length,
p: subP
})
)
.filter(i=>i.similar>0)
.sort((a, b)=>b.similar - a.similar);
if(similar.length){
let maxCount = similar[ 0 ].similar;
let minCount = minSimilarTags || Math.round( maxCount - maxCount / 4 );
similarTags[ tagsStr ] = similar
.filter( s => s.similar >= minCount )
.map(s=>s.p)
}
});
similarClusters = Object.keys(similarTags)
.map(k=>({k, v:similarTags[k]}))
.filter( a => a.v.length >= minAmount);
log.push(`Clusters that have >= ${minSimilarTags} similar tags: `+Object.values(similarClusters).length);
let randed = rand(similarClusters);
log.push(`Used cluster tags: ${randed.k.split(',').map(id=>dP.tagsHash[id].name).join(', ')}`);
filtered = randed.v;
}
let result;
if(single){
result = rand(filtered);
log.push('Matched product: '+result.title);
}else{
if(filtered.length < minAmount){
log.push(`Not enough components meeting criteria`);
return false;
}
result = shuffle(filtered).slice(0, doNotTrim?filtered.length:rand(minAmount, maxAmount));
log.push('Base products:');
result.forEach(p => log.push(` > ${p.title}`))
log.push('');
}
return result;
},
subdivideComponents: function(from, sub) {
const hash = {};
from.forEach(c=>hash[normalizeText(c.name)] = c);
sub.forEach(c=>delete hash[normalizeText(c.name)]);
return Object.values(hash);
},
getUniqComponents: function(products) {
return Object.values(qB.getUniqComponentsHash(products));
},
getUniqComponentsHash: function(products) {
const hash = {};
products.forEach(
p=>
p
.getComponents()
.forEach(c=>
hash[normalizeText(c.name)] = c
)
);
return hash;
},
getComponentsWithSharedProducts: function(
products,
minSharedProductCount,
maxSharedProductCount
) {
const hash = {};
minSharedProductCount = minSharedProductCount || 0;
maxSharedProductCount = maxSharedProductCount || Infinity;
products.forEach(
p=>
p
.getComponents()
.forEach(c=> {
const name = normalizeText( c.name );
(hash[ name ] || (hash[name] = [])).push({c, p});
})
);
return Object
.values(hash)
.filter(i =>
i.length >= minSharedProductCount &&
i.length <= maxSharedProductCount
)
.map(i=>({component: i[0].c, products: i.map(({p})=>p)}));
},
getComponentsNotInCorrect: function(income) {
return shuffle(
qB.subdivideComponents(
qB.getUniqComponents(income.wrong),
income.correct.getComponents()
)
);
},
getProductsWithTags: function({
products,
tags,
minMatch
}){
if(minMatch === void 0){
minMatch = tags.length;
}
const tHash = tags.reduce((s, t)=>{s[t.id] = t; return s;}, {});
products = products || Object.values( dP.products );
return products
.filter(
p => p.getTags()
.filter(t=>t.id in tHash).length >= minMatch
);
},
prebuild: {
similarTaggedProductWithPhotoAndComponents: {
fn: function(log) {
this.getEmAll = true;
this.products = {min: 2};
const possibilities = shuffle(
qB.prebuild.similarTaggedProductWithPhoto.fn.call(this, log)
);
if(!possibilities || !possibilities.length)
return false;
for( let i = 0, _i = possibilities.length; i < _i; i++ ){
const possibility = possibilities[ i ];
const components = possibility.correct.getComponents();
const other = qB.getUniqComponentsHash(possibility.all.filter( sP => sP !== possibility.correct ))
components.forEach(c=>delete other[normalizeText(c.name)]);
if(Object.keys(other).length >= this.answers.min){
log.push('Take '+possibility.baseProduct.title+' with uniq components:');
components.forEach(c=>
log.push(` > ${c.name}`)
);
log.push('');
log.push('Other components in group tagged as '+
possibility.baseProduct.getTags().map(t=>t.name).join(', ')+':'
);
Object.values( other ).forEach(c=>
log.push(` > ${c.name}`)
);
const c = rand( components )
return {
allCorrect: components,
baseProduct: possibility.baseProduct,
correct: c,
wrong: shuffle( Object.values( other ) ).slice( 0, rand( this.answers.min - 1, this.answers.max - 1 ) ),
allWrong: Object.values( other )
};
}
}
return false;
}
},
similarTaggedProductWithPhoto: {
questionMinComponentsCount: Number,
products: {min: Number, max: Number},
fn: function(log) {
let allProducts = Object.values(dP.products);
let products = Object.values(dP.products)
.filter(p=>p.image)
.filter(p=>p.getComponents().length>=this.questionMinComponentsCount)
log.push( `Products with image and >= ${this.questionMinComponentsCount} components count: ${products.length}` );
if(products.length === 0){
rand(Object.values(dP.products)).image = 'https://robohash.org/'+Math.random().toString(36)
return false;
}
const possibility = products.map(p=>{
const similar = qB.getProductsWithTags({
products: allProducts,
tags: p.getTags(),
minMatch: 1
});
if(similar.length+1 >= this.products.min ){
return {
correct: p,
baseProduct: p,
wrong: similar.filter( sP => sP !== p ).slice(0,rand( this.products.min - 1,this.products.max - 1)),
all: similar
};
}else{
return false;
}
})
.filter(Boolean);
log.push( `Possible count: ${possibility.length}` );
return this.getEmAll ? possibility : rand(possibility);
}
},
similarTaggedProducts: {
// minimal components in product
questionCmpAmount: 4,
// amount of matched products
products: {from: 4, to: 8},
// product similarity (minimal matched tags count)
minSimilarTags: 2,
fn(log){
let products = qB.randomProduct({
minComponents: this.questionCmpAmount,
amount: this.products,
connectedByTags: true,
minSimilarTags: this.minSimilarTags
}, log);
for( let i = 0, _i = products.length; i < _i; i++ ){
const product = products[ i ];
/* if(this.withPhoto && !product.image)
continue;*/
const cmps = product
.getComponents()
.filter(cmp => products.filter(p=>p.containsComponent(cmp)).length === 1)
if(cmps.length>=this.questionCmpAmount){
if(this.doNotLogUnique){
log.push( `Product ${lapk( product.title )} have ${cmps.length} uniq components:` );
}else{
log.push( `Product ${lapk( product.title )} have >= ${this.questionCmpAmount} uniq components (${cmps.length}):` );
}
const shuffled = shuffle(cmps.slice()),
used = shuffled.slice(0, this.questionCmpAmount);
cmps.forEach(c=>log.push(`${!this.doNotLogUnique && used.indexOf(c)>-1?'+':' '} > ${c.name}`));
let wrong = products.slice();
wrong.splice(i,1);
return {
baseProduct: product,
correct: product,
wrong: wrong,
uniq: used,
allUniq: shuffled
}
}
}
return false;
}
}
}
};
\ No newline at end of file
quizTypes.radio = [
{
questionCmpAmount: 4,
probability: 10,
type: 'Product contains 4 components',
products: {from: 4, to: 8},
minSimilarTags: 1,
from: qB.prebuild.similarTaggedProducts.fn,
question(income, log){
return 'В какой из продуктов входят данные ингредиенты: '+income.uniq.map(a=> textFormat(a.name)).join(', ');//products.map(p=>p.title)
},
answer( income, log ){
return shuffle(
[new Answer.Correct(textFormat(income.correct.title))]
.concat(income.wrong.map(p=>new Answer.Wrong(textFormat(p.title)))))
}
},
{
questionCmpAmount: 2,
probability: 8,
type: 'Component in product',
products: {from: 4, to: 6},
minSimilarTags: 1,
from: qB.prebuild.similarTaggedProducts.fn,
question(income, log){
return `Выберите один ингредиент, который входит в продукт ${lapk(income.correct.title)}`
},
answer( income, log ){
return shuffle(
[new Answer.Correct(textFormat(shuffle(income.uniq)[0].name))]
.concat(
shuffle(
qB.subdivideComponents( qB.getUniqComponents(income.wrong), income.correct.getComponents() )
)
.slice(0, rand(this.products.from-1, this.products.to-1))
.map(c=>new Answer.Wrong(textFormat(c.name)))
)
)
}
},
{
questionCmpAmount: 0,
probability: 8,
type: 'Select product description',
products: {from: 3, to: 5},
minSimilarTags: 1,
from: qB.prebuild.similarTaggedProducts.fn,
question(income, log){
return `Какое из этих описаний относится к продукту ${lapk(income.correct.title)}`
},
answer( income, log ){
return shuffle(
[new Answer.Correct(textFormat(income.correct.description))]
.concat(
income.wrong.map(
p => new Answer.Wrong(textFormat(p.description))
)
))
}
},
{
questionCmpAmount: 0,
probability: 8,
type: 'Select product that matches description',
products: {from: 4, to: 6},
minSimilarTags: 1,
from: qB.prebuild.similarTaggedProducts.fn,
question(income, log){
return `К какому из продуктов относится это описание:\n${textFormat(income.correct.description)}`
},
answer( income, log ){
return shuffle(
[new Answer.Correct(textFormat(income.correct.title))]
.concat(
income.wrong.map(
p => new Answer.Wrong(textFormat(p.title))
)
))
}
},
{
probability: 10,
answers: {from: 3, to: 6},
question: ({product})=> `Выберите лишний ингредиент, НЕ входящий в продукт "${product.title}"`,
type: 'Do not contain',
from: (log)=>{
const product = qB.randomProduct({ minComponents: 2, single: true }, log);
return {baseProduct: product, product}
},
answer( {product}, log ){
const out =
shuffle(
dP.Product.getComponents(product)
.map(i=>({correct: false, text: i.name}))
)
.slice(0, rand(this.answers.from - 1, this.answers.to - 1)),
// get tags
tags = product.tags,
donors = [];
log.push(`Have ${dP.Product.getComponents(product).length} components. Took ${out.length}:`);
out.forEach(function(a) {
log.push(' > '+ a.text)
});
log.push('\nTags: '+dP.Product.getTags(product).map(t=>t.name).join(', '));
Object.keys(dP.products)
.map(k=>dP.products[k])
.forEach(item => {
if(item.id === product.id)
return;
const matchedCount = tags.filter(tid=>item.tags.indexOf(tid)>-1).length;
if(matchedCount>0){
donors.push({
count: matchedCount,
item,
title: item.title,
component: dP.Product.getComponents(item).map(a=>a.name)
});
}
});
log.push('Matched '+donors.length+' component donors.');
donors.sort((a,b)=>b.count-a.count);
if(!donors.length){
log.push('Not enough');
return false;
}
const maxCount = donors[0].count;
const minCount = Math.round(maxCount - maxCount/4);
const alreadyExist = out.reduce((store, i)=>{
store[normalizeText(i.text)] = true;
return store;
}, {'соль': true,'сольпощово': true, 'сохор': true});
log.push('Nearest candidate have got '+ maxCount +' similar tag.');
const may = ([].concat.apply(
[],
donors
.filter(i=>i.count > 0 && i.count >= minCount)
.map(i=>
i.component
.filter(c=>{
if(!c.trim())
return false;
const normalized = normalizeText(c);
if(normalized in alreadyExist)
return false;
alreadyExist[normalized] = true;
return true;
})
.map(x => ({count: i.count, item: i.item, text: x}))
)
));
log.push('Filtered candidates with ['+ (minCount===maxCount? minCount : minCount+' - '+ maxCount) +'] similar tags.');
if(may.length<2)
return false;
const answer = rand(may);
log.push('Use '+ answer.item.title +' as donor.');
log.push('Because it has '+ answer.count +' similar tags: '+ dP.Product.getTags(answer.item).map(t=>t.name).join(', '));
out.push({correct: true, text: answer.text});
log.push('\n << '+ answer.text);
return out;
}
}];
\ No newline at end of file
quizTypes.radioPhoto = [
{
questionMinComponentsCount: 0,
probability: 10,
type: 'What is it',
products: {min: 4, max: 6},
from: qB.prebuild.similarTaggedProductWithPhoto.fn,
question(income, log){
return 'Какой продукт изображен на картинке?'
},
answer( income, log ){
return shuffle(
[new Answer.Correct(textFormat(income.correct.title))]
.concat(income.wrong.map(p=>new Answer.Wrong(textFormat(p.title)))))
}
},
{
questionMinComponentsCount: 0,
probability: 8,
type: 'What describes it better',
products: {min: 3, max: 5},
from: qB.prebuild.similarTaggedProductWithPhoto.fn,
question(income, log){
return 'Какое из этих описаний относится к продукту, изображенному на картинке?'
},
answer( income, log ){
return shuffle(
[new Answer.Correct(textFormat(income.correct.description))]
.concat(income.wrong.map(p=>new Answer.Wrong(textFormat(p.description)))))
}
},
{
questionMinComponentsCount: 2,
probability: 3,
type: 'What component is in it',
answers: {min: 4, max: 6},
from: qB.prebuild.similarTaggedProductWithPhotoAndComponents.fn,
question(income, log){
return 'Выберите один ингредиент, который входит в продукт, изображенный на картинке'
},
answer( income, log ){
return shuffle(
[new Answer.Correct(textFormat(income.correct.name))]
.concat(income.wrong.map(p=>new Answer.Wrong(textFormat(p.name)))))
}
}
];
\ No newline at end of file
const lapk = (text)=>`„${text}“`;
const rand = function(a, b){
if(Array.isArray(a)){
return a[Math.random.seeded()*a.length|0];
}
if(typeof a === 'object' && 'max' in a){
b = a.max;
a = a.min || 0;
}
a = Math.ceil(a);
b = Math.floor(b);
return Math.floor(Math.random.seeded() * (b - a + 1)) + a;
return (r-a)*(b-a)|0;
},
probabilityRand = function(items) {
let sum = 0;
for( let i = 0, _i = items.length; i < _i; i++ ){
const item = items[ i ];
sum += item.probability |0;
}
const theRandom = Math.random.seeded();
let total = 0;
for( let i = 0, _i = items.length; i < _i; i++ ){
const item = items[ i ];
total += item.probability/sum;
if(theRandom < total)
return item;
}
};
const normalizeText = function(text) {
return text.trim().toLowerCase().replace(/[^а-я]/g,'').replace(/[аоуеэюёиыя]+/g,'о')
};
const quizTypes = {
checkbox: [],
checkboxPhoto: {
},
radio: [],
radioPhoto: {
}
};
const shuffle = function (a) {
for (let i = a.length - 1; i > 0; i--) {
const j = Math.floor(Math.random.seeded() * (i + 1));
[a[i], a[j]] = [a[j], a[i]];
}
return a;
};
const standardGenerator = function(random) {
const initialSeed = Math.random.seeded.getStringSeed();
const log = quizGenerator.log = ['Random seed: '+initialSeed];
const questionID = random(Object.values(dP.standardQuestions)),
q = dP.standardQuestions[questionID];
log.push('Creating standard question.');
log.push('It would be question number '+questionID);
log.push('');
log.push('Title '+q.title);
q.answers.forEach(
a =>
log.push((a.correct?'+':' ')+` > ${a.title}`));
return {
type: q.multiple ? 'checkbox': 'radio',
categoryId: 2,
productId: q.cardInfoID,
question: textFormat(q.title),
answers: shuffle(q.answers.map(a =>
a.correct ?
new Answer.Correct(a.title) :
new Answer.Wrong(a.title))),
image: !q.image?null:q.image,
log
};
};
const quizGenerator = function(type, photo, subType) {
return _quizGenerator(type, photo, subType);
},
_quizGenerator = function(type, photo, subType, attempt) {
attempt = attempt || 0;
const initialSeed = Math.random.seeded.getStringSeed();
let log;
if(attempt === 0){
const types = quizTypes[type+(photo?'Photo':'')];
subType = probabilityRand(types);
log = quizGenerator.log = ['Random seed: '+initialSeed,'Quiz generate '+type+(photo ? ' with photo':'') +' '+(subType?`${lapk(subType.type)}`:'. FAIL')];
}else if(attempt<30){
log = quizGenerator.log;
log.push('\nATTEMPT FAILED!\n\n'+
'------------------------\n' +
' ATTEMPT '+ (attempt+1)+'\n'+
'------------------------\n\n'
)
}else{
throw new Error('Too much errors')
}
const cfg = subType;
if(!cfg)
return {answers: [], log};
const source = cfg.from.call(cfg, log);
if(source === false){
// давай по новой
return _quizGenerator( type, photo, subType, attempt + 1 );
}
let answers = shuffle(cfg.answer.call(cfg, source, log));
if(answers === false){
// давай по новой
return _quizGenerator( type, photo, subType, attempt + 1 );
}
return {
seed: initialSeed,
type,
categoryId: 1,
productId: source.baseProduct.id,
question: cfg.question.call(cfg, source, log),
answers,
image: !photo?null:source.baseProduct.image,
log,
}
};
const Answer = {
Correct: function(val) {
this.correct = true;
this.text = val;
},
Wrong: function(val) {
this.correct = false;
this.text = val;
}
};
\ No newline at end of file
dP.Product = {
getByID: function(id) {
return dP.products[id];
},
getComponents: function(product) {
return dP.componentsListHashByProduct[ product.id ] || [];
},
getTags: function(product) {
return product.tags.map(dP.Tag.getByID);
},
get: function() {
},
ctor: function(data) {
this.tags = [];
Object.assign(this, data);
}
};
dP.Product.ctor.prototype = {
getComponents(){return dP.Product.getComponents(this);},
getTags(){return dP.Product.getTags(this);},
containsComponent(cmp, fuzzy){
if(!fuzzy)
return this.getComponents().indexOf(cmp)>-1;
const normalized = normalizeText(cmp.name);
return this.getComponents().filter(sCmp=>normalized === normalizeText(sCmp.name) ).length > 0;
}
};
\ No newline at end of file
const useSubSub = true;
const dataProvider = {
maxConnection: 0,
maxTagID: 0,
slice: {},
standardQuestions: {},
tags: [],
connections: {},
products: {}
},
dP = dataProvider,
initDataProvider = function(data) {
Object.assign(dP, data);
let key;
for(key in data.products){
data.products[key] = new dP.Product.ctor(data.products[key])
}
Object.assign(dP, {
componentsList: [],
componentsListHashByProduct: {}
});
for(key in data.components){
const cmp = data.components[key];
cmp.tags = [];
dP.componentsList.push(cmp);
(dP.componentsListHashByProduct[cmp.id] || (dP.componentsListHashByProduct[cmp.id] = [])).push(cmp);
}
if(useSubSub){
data.tags = [];
data.connections = [];
dP.tagsHash = {};
Object.values( data.products ).forEach( function( item ){
const tag = dP.Tag.getOrCreate( item.subsubcat );
dP.Tag.connectToProduct( dP.Product.getByID( item.id ), tag );
} );
}else{
dP.tagsHash = data.tags.reduce( ( store, item ) => {
dP.maxTagID = Math.max( dP.maxTagID, item.id );
store[ item.id ] = item;
return store
}, {} );
data.connections.forEach( ( connection ) => {
dP.maxConnection = Math.max( dP.maxConnection, connection.cid );
if( connection.type === 0 ){
data.products[ connection.eid ].tags.push( connection.tag )
}
} );
}
};
dP.slice.productTable = {
getItems: function() {
return Object.keys(dP.products)
.map(k=>dP.products[k])
.map(item=>{
dP.slice.productTable.updateFilterCache(item);
return item;
})
},
updateFilterCache: function(item) {
item._titleFilter = item.title.toLowerCase();
item._componentsFilter = (dP.componentsListHashByProduct[item.id] || []).map(a=>a.name).join(' ');
item._tagsFilter = (item.tags || []).map(t=>dP.tagsHash[t].name).join(' ');
}
};
\ No newline at end of file
dP.Tag = {
getOrCreate: function(val) {
const matched = dP.Tag.getByName(val);
if(matched)
return matched;
// create
const tagID = ++dP.maxTagID,
item = {
id: tagID,
name: val
};
dP.tagsHash[tagID] = item;
dP.tags.push(item);
return item;
},
remove: function(tagID) {
dP.tags.splice(dP.tags.indexOf(dP.tagsHash[tagID]), 1);
delete dP.tagsHash[tagID];
},
getByID: function(tagID) {
return dP.tagsHash[tagID]
},
getByName: function(name) {
return dP.tags.filter(a=>a.name === name)[0];
},
disconnectFromProduct: function(product, tag) {
const tagID = tag.id;
if(product.tags.indexOf(tagID) > -1){
product.tags.splice( product.tags.indexOf(tagID), 1 );
const connection = dP.connections
.filter(connection =>
connection.tag === tagID &&
connection.type === 0 &&
connection.eid === product.id
)[0];
if(!connection)
return;
if(connection.cid === dP.maxConnection-1)
dP.maxConnection--;
dP.connections.splice(
dP.connections.indexOf(
connection
) ,1);
dP.Tag.removeIfNoConnections(tag);
dP.slice.productTable.updateFilterCache(product);
}
},
connectToProduct: function(product, tag) {
const tagID = tag.id;
if(product.tags.indexOf(tagID) === -1){
product.tags.push( tagID );
dP.connections.push({
cid: ++dP.maxConnection,
type: 0,
eid: product.id,
tag: tagID
});
dP.slice.productTable.updateFilterCache(product);
}
},
removeIfNoConnections: function({tagID}) {
if(dP.connections.filter(connection=>connection.tag === tagID).length === 0){
dP.Tag.remove(tagID)
}
}
};
\ No newline at end of file
// Pico pico graph!
var PCG = window['PCG'] = function(cfg) {
if(cfg.consts){
this.consts = PCG.apply(PCG.apply({}, this.consts), cfg.consts);
delete cfg.consts;
}
PCG.apply(this, cfg);
this._update = this._update.bind(this);
this.resize = this.resize.bind(this);
this.init();
};
PCG.apply = function(a,b) {
for(var k in b){
a[k] = b[k];
}
return a;
};
PCG.ZOOM = {
PIE: 1,
CUSTOM: 2,
LOAD: 3
};
\ No newline at end of file
const D = PCG.D;
const {div, span} = D;
const view = {
page: {},
cmp: {}
};
\ No newline at end of file
D.declare('view.cmp.Menu',({title, id, key, cls}) =>
<div cls={_ => store.equal(key, id,active => _( 'main-menu-item', cls,
'main-menu-item__'+D.escapeCls(id),
{'main-menu-item__active': active}
))}
onclick={()=> store.set(key, id)}>{title}</div>
);
\ No newline at end of file
view.page.Components = function(){
let componentTable;
this.dom = div( { cls: 'content-component' },
div({cls: 'product-filter__area title-gradient'},
div({cls: 'product-filter'},
div({cls: 'product-filter__title'},'Фильтр'),
D.input({
attr: {
value: (update)=>store.sub('componentFilterText', (val)=>update(val))},
cls: 'product-filter__input',
on: {input: (e)=>store.set('componentFilterText', e.target.value)}})
)
),
componentTable = new view.cmp.Table( {
sorters: [ { id: 'title', type: String } ],
sort: [ { name: 'asc' } ],
filterFn: function( text, me ){
return !text || textFilterMatched( this.name, text )
},
afterFilter: ( items ) => store.set( 'componentFilteredCount', items.length ),
itemTpl: ( item, me, bonus ) => {
const dom = div( { cls: 'table-item table-item__component' },
span( {
cls: 'component-title',
on: { click: tableAction },
attr: { 'data-action': 'toggle:' + item.id }
}, me.highlight( item.name ), ' ≡' )
);
return dom;
},
items: dP.componentsList
} )
);
const tableAction = componentTable.getActionDelegate();
store.sub([
'componentFilterText'
], function(text) {
componentTable.filter(text.trim().toLowerCase());
componentTable.updateChildren();
});
};
\ No newline at end of file
//import f from './exp.js';
//console.log(f)
D.declare('view.page.Export', function(){
return <div cls="export-panel">
<div cls="title-gradient">
<div cls="export-sub-menu">
<div cls={_=>_("export-sub-menu", 'olo')}>
<view.cmp.Menu title='Тэги' id='tags' key='exportData'/>
<view.cmp.Menu title='Связи' id='connections' key='exportData'/>
</div>
<div cls="export-sub-menu">
{view.cmp.Menu( { title: 'CSV', id: 'csv', key: 'exportFormat' } )}
{view.cmp.Menu( { title: 'JSON', id: 'json', key: 'exportFormat' } )}
{view.cmp.Menu( { title: 'YAML', id: 'yaml', key: 'exportFormat' } )}
{view.cmp.Menu( { title: 'SQL', id: 'sql', key: 'exportFormat' } )}
</div>
</div>
</div>
{exportLogic( <textarea cls='export-data'/>)}
<div cls="export-comment">Connetction type{'<Enum>'}: 0 — продукт, 1 — компонент`</div>
<button onclick={e => console.log( e )}>btn1</button>
</div>
});
view.page.Export = function() {
this.dom =
div({cls: 'export-panel'},
div({cls: 'title-gradient'},
div({cls: 'export-sub-menu'},
div({cls: 'export-sub-menu'},
view.cmp.Menu({title: 'Тэги', id: 'tags', key: 'exportData'}),
view.cmp.Menu({title: 'Связи', id: 'connections', key: 'exportData'})
),
div({cls: 'export-sub-menu'},
view.cmp.Menu({title: 'CSV', id: 'csv', key: 'exportFormat'}),
view.cmp.Menu({title: 'JSON', id: 'json', key: 'exportFormat'}),
view.cmp.Menu({title: 'YAML', id: 'yaml', key: 'exportFormat'}),
view.cmp.Menu({title: 'SQL', id: 'sql', key: 'exportFormat'}),
)
)
),
exportLogic(D.textarea({cls: 'export-data'})),
div({cls: 'export-comment'}, `Connetction type<Enum>: 0 — продукт, 1 — компонент`)
);
};
\ No newline at end of file
view.cmp.Answer = function(answer, type) {
return (
<label cls={D.cls('quiz-answer-label', 'quiz-answer-label__'+type)}>
<input type={type} checked={answer.correct}/>{answer.text}
</label>
)
};
view.page.Generate = function() {
const update = function() {
const photo = store.get('generatePhoto') === 'photo',
type = store.get('generateType'),
category = store.get('generateCategory');
try{
seedInput.value = Math.random.seeded.getStringSeed();
const result = category === 'product' ? quizGenerator( type, photo ) : standardGenerator(({length})=>rand(0,length-1));
title.innerHTML = textFormat( result.question, true );
image.innerHTML = result.image ? `<img src="${result.image}" alt="img"/>` : '';
image.style.display = result.image ? 'block': 'none';
debug.value = result.log.join( '\n' )
D.removeChildren( answers );
D.appendChild( answers, result.answers.map( ( a ) => view.cmp.Answer( a, type ) ) );
}catch( e ){
debug.value = quizGenerator.log.join( '\n' )
console.error(e)
}
};
let title, answers, debug, seedInput, image;
this.dom =
<div cls='generate-panel'>
<div cls='title-gradient'>
<div cls='generate-sub-menu'>
<div cls='generate-sub-menu'>
{view.cmp.Menu({title: 'Продукты', id: 'product', key: 'generateCategory'})}
{view.cmp.Menu({title: 'Стандарты', id: 'standard', key: 'generateCategory'})}
</div>
{view.cmp.Switch({key: 'generateCategory'}, {
product: <div cls='generate-sub-menu'>
<div cls='generate-sub-menu'>
{view.cmp.Menu({title: 'Единственный', id: 'radio', key: 'generateType'})}
{view.cmp.Menu({title: 'Множественный', id: 'checkbox', key: 'generateType'})}
</div>
<div cls='generate-sub-menu'>
{view.cmp.Menu({title: 'С фото', id: 'photo', key: 'generatePhoto'})}
{view.cmp.Menu({title: 'Без фото', id: 'noPhoto', key: 'generatePhoto'})}
</div>
</div>
})}
</div>
</div>
<div cls='generate-controls'>
<input type='button' value='F5' onclick={update}/>
<span cls='generate-seed-label'>Seed:</span>
{seedInput = <input
cls='seed'
type='input' value='' placeholder='seed'
oninput={function() {
var val = seedInput.value;
Math.random.seeded.setStringSeed(val);
update()
}}/>
}
</div>
<div cls='generate-example'>
{title = <div cls='generate-title'/>}
{image = <div cls='generate-image'/>}
{answers = <div cls='generate-answers'/>}
{debug = <textarea cls='generate-debug'/>}
</div>
</div>;
store.sub(['generateType', 'generatePhoto', 'generateCategory'], update);
};
\ No newline at end of file
view.cmp.Answer = function(answer, type) {
return D.label({cls: D.cls('quiz-answer-label', 'quiz-answer-label__'+type)},
D.input({
attr: {type, checked: answer.correct}
}),
answer.text
)
};
view.page.Generate = function() {
const update = function() {
const photo = store.get('generatePhoto') === 'photo',
type = store.get('generateType'),
category = store.get('generateCategory');
try{
seedInput.value = Math.random.seeded.getStringSeed();
const result = category === 'product' ? quizGenerator( type, photo ) : standardGenerator(({length})=>rand(0,length-1));
title.innerHTML = textFormat( result.question, true );
image.innerHTML = result.image ? `<img src="${result.image}" alt="img"/>` : '';
image.style.display = result.image ? 'block': 'none';
debug.value = result.log.join( '\n' )
D.removeChildren( answers );
D.appendChild( answers, result.answers.map( ( a ) => view.cmp.Answer( a, type ) ) );
}catch( e ){
debug.value = quizGenerator.log.join( '\n' )
console.error(e)
}
};
let title, answers, debug, seedInput, image;
this.dom =
div({cls: 'generate-panel'},
div({cls: 'title-gradient'},
div({cls: 'generate-sub-menu'},
div({cls: 'generate-sub-menu'},
view.cmp.Menu({title: 'Продукты', id: 'product', key: 'generateCategory'}),
view.cmp.Menu({title: 'Стандарты', id: 'standard', key: 'generateCategory'})
),
view.cmp.Switch({key: 'generateCategory'}, {
product: div({cls: 'generate-sub-menu'},
div({cls: 'generate-sub-menu'},
view.cmp.Menu({title: 'Единственный', id: 'radio', key: 'generateType'}),
view.cmp.Menu({title: 'Множественный', id: 'checkbox', key: 'generateType'})
),
div({cls: 'generate-sub-menu'},
view.cmp.Menu({title: 'С фото', id: 'photo', key: 'generatePhoto'}),
view.cmp.Menu({title: 'Без фото', id: 'noPhoto', key: 'generatePhoto'})
)
)
})
)
),
div({cls: 'generate-controls'},
D.input({
attr: {type: 'button', value: 'F5'},
on: {click: update}}),
span({cls: 'generate-seed-label'}, 'Seed:'),
seedInput = D.input({
cls: 'seed',
attr: {type: 'input', value: '', placeholder: 'seed'},
on: {input: function() {
var val = seedInput.value
Math.random.seeded.setStringSeed(val);
update()
//Math.random.seeded.setStringSeed(val);
}}}),
),
div({cls: 'generate-example'},
title = div({cls: 'generate-title'}),
image = div({cls: 'generate-image'}),
answers = div({cls: 'generate-answers'}),
debug = D.textarea({cls: 'generate-debug'})
)
);
store.sub(['generateType', 'generatePhoto', 'generateCategory'], update);
};
\ No newline at end of file
view.page.Products = function() {
let productTable, productFilterByTitle, productFilterByComponent, productFilterByTag, productFilterText,
tagField;
this.dom = div({cls: 'content-products'},
div({cls: 'product-filter__area title-gradient'},
div({cls: 'product-filter'},
div({cls: 'product-filter__title'},'Фильтр'),
D.input({
attr: {
value: (update)=>store.sub('productFilterText', (val)=>update(val))},
cls: 'product-filter__input',
on: {input: (e)=>store.set('productFilterText', e.target.value)}})
),
div({cls: 'product-filter'},
span({cls: 'product-filter__hint'},'Область фильтрации:'),
D.label({cls: 'product-filter__label'},
D.input({
attr:{
type: 'checkbox',
checked: (update)=>store.sub('productFilterByTitle', (val)=>update(val))},
on: {change: (e)=>{store.set('productFilterByTitle', e.target.checked)}}
}), 'Название'
),
D.label({cls: 'product-filter__label'},
D.input({
attr:{
type: 'checkbox',
checked: (update)=>store.sub('productFilterByComponent', (val)=>update(val))},
on: {change: (e)=>{store.set('productFilterByComponent', e.target.checked)}}
}), 'Ингредиенты'
),
D.label({cls: 'product-filter__label'},
D.input({
attr:{
type: 'checkbox',
checked: (update)=>store.sub('productFilterByTag', (val)=>update(val))},
on: {change: (e)=>{store.set('productFilterByTag', e.target.checked)}}
}), 'Тэги'
)
)
),
div({cls: 'tag-manipulations'},
D.input({attr:{type: 'button', value: 'Удалить тэг'}, on: {
click: ()=> {
var val = tagField.getValue().trim();
if(val!==''){
var tag = dP.Tag.getByName(val);
if(!tag)
return;
productTable.getSelected().forEach(function(item) {
dP.Tag.disconnectFromProduct(dP.Product.getByID(item.id), tag);
productTable.updateChildByData(item);
});
store.set('lastUpdate', +new Date())
}
}}}),
tagField = new view.cmp.TagField(),
D.input({
cls: update =>
store.sub('productFilteredCount', count =>
update(D.cls('tag-manipulations-add-tag', {
'tag-manipulations__not-active': count === 0 || count > 30}))),
attr:{type: 'button', value: 'Добавить тэг'},
on: {
click: ()=> {
var val = tagField.getValue().trim();
if(val==='')
return;
const tag = dP.Tag.getOrCreate(val);
productTable.getSelected().forEach(function(item) {
dP.Tag.connectToProduct(dP.Product.getByID(item.id), tag);
productTable.updateChildByData(item);
});
store.set('lastUpdate', +new Date())
}
}
}),
D.span({cls: 'tag-manipulations-add-tag__comment'},
_=> store.sub('productFilteredCount', count =>
_(
'Найдено: '+count,
count === 0 ? D.span({}, 'Надо больше результатов!') : (count > 30 ? D.span({}, 'Не цепляй тэг на всё сразу!') : null)
)))
),
productTable = new view.cmp.Table({
sorters: [{id: 'title', type: String}],
sort: [{title: 'asc'}],
filterBy: {title: true, tag: true, component: true},
filterFn: function(text, me) {
return !text ||
(me.filterBy.title && textFilterMatched(this._titleFilter, text)) ||
(me.filterBy.tag && textFilterMatched(this._tagsFilter,text)) ||
(me.filterBy.component && textFilterMatched(this._componentsFilter,text))
},
afterFilter: (items)=>store.set('productFilteredCount', items.length),
itemTpl: (item, me, bonus) => {
const dom = div({cls: 'table-item table-item__product'},
div( {
cls: 'table-item-action table-item-collapse',
attr: { title: 'Скрыть', 'data-action': 'hide:' + item.id },
on: { click: tableAction }
}, '←' ),
span( {
cls: 'product-title',
on: { click: tableAction },
attr:{ 'data-action': 'toggle:'+ item.id}
}, me.highlight( item.title ),' ≡' ),
( item.tags || [] ).map( t => me.highlight( dP.tagsHash[ t ].name ) ).map( view.cmp.Tag ),
bonus.full && D.html( { cls: 'product-description' }, textFormat(item.description, true) ),
div(
{ cls: 'table-item__product-components' },
D.join( ( dP.componentsListHashByProduct[ item.id ] || [] ).map( a => D.span( { cls: 'product-cmp' }, me.highlight( a.name ) ) ), ', ' )
)
);
if(bonus.hidden){
if(bonus.hiddenFull){
dom.classList.add( 'table-item__hidden-full' );
}else{
setTimeout( () => {
dom.classList.add( 'table-item__hidden' );
}, 1 )
setTimeout( () => {
bonus.hiddenFull = true;
dom.classList.add( 'table-item__hidden-full' );
}, 300 )
}
}
return dom;
},
items: dP.slice.productTable.getItems()
})
);
const tableAction = productTable.getActionDelegate();
store.sub([
'productFilterText',
'productFilterByTitle',
'productFilterByComponent',
'productFilterByTag'
], function(text, title, component, tag) {
Object.assign(productTable.filterBy, {title, component, tag});
productTable.filter(text.trim().toLowerCase());
productTable.updateChildren();
});
};
\ No newline at end of file
var Observable = function() { var Observable = function() {
this._listeners = {}; this._listeners = {};
}; };
Observable.prototype = { Observable.prototype = {
on: function(k, v) { on: function(k, v) {
(this._listeners[k] || (this._listeners[k] = [])).push(v); (this._listeners[k] || (this._listeners[k] = [])).push(v);
var _self = this; var _self = this;
return function ReleaseObservable() { return function ReleaseObservable() {
_self.un(k, v); _self.un(k, v);
}; };
}, },
un: function(k, v) { un: function(k, v) {
var list = this._listeners[k]; var list = this._listeners[k];
if(list){ if(list){
var id = list.indexOf(v); var id = list.indexOf(v);
if(id > -1){ if(id > -1){
list.splice(id, 1); list.splice(id, 1);
} }
} }
}, },
fire: function(k) { fire: function(k) {
var listeners = this._listeners[k]; var listeners = this._listeners[k];
if(listeners === void 0) if(listeners === void 0)
return; return;
for( var i = 0, _i = listeners.length; i < _i; i++ ){ for( var i = 0, _i = listeners.length; i < _i; i++ ){
const listener = listeners[ i ]; const listener = listeners[ i ];
listener.apply(this, [].slice.call(arguments, 1)); listener.apply(this, [].slice.call(arguments, 1));
} }
} }
}; };
// It is mostly node.js posix path.
window.__Path = (function(){ window.__Path = (function(){
var splitPathRe = var splitPathRe =
......
...@@ -4,7 +4,7 @@ function mulberry32(a) { ...@@ -4,7 +4,7 @@ function mulberry32(a) {
t = Math.imul(t ^ t >>> 15, t | 1); t = Math.imul(t ^ t >>> 15, t | 1);
t ^= t + Math.imul(t ^ t >>> 7, t | 61); t ^= t + Math.imul(t ^ t >>> 7, t | 61);
return ((t ^ t >>> 14) >>> 0) / 4294967296; return ((t ^ t >>> 14) >>> 0) / 4294967296;
} };
out.getSeed = function() { out.getSeed = function() {
return a; return a;
......
const checkSpecialCharsAndEmpty = (value) => { const checkSpecialCharsAndEmpty = (value) => {
const thisValue = value.toString().toLowerCase(); const thisValue = value.toString().toLowerCase();
let hasSpecialChars = false; let hasSpecialChars = false;
if (typeof value === 'string') { if (typeof value === 'string') {
hasSpecialChars = thisValue.includes('\n') hasSpecialChars = thisValue.includes('\n')
|| thisValue.includes('\t') || thisValue.includes('\t')
|| thisValue.includes(',') || thisValue.includes(',')
|| thisValue.includes(';') || thisValue.includes(';')
|| thisValue.includes('.') || thisValue.includes('.')
|| thisValue.includes('"') || thisValue.includes('"')
|| thisValue.includes('\'') || thisValue.includes('\'')
|| thisValue.includes('`') || thisValue.includes('`')
|| thisValue.includes('´') || thisValue.includes('´')
|| thisValue.includes(' ') || thisValue.includes(' ')
|| thisValue.length === 0; || thisValue.length === 0;
} }
return hasSpecialChars; return hasSpecialChars;
}; };
const separatorOrLineBreak = (length, elementIdx, separator) => ( const separatorOrLineBreak = (length, elementIdx, separator) => (
length - 1 === elementIdx ? '\n' : separator length - 1 === elementIdx ? '\n' : separator
); );
const escapeDoubleQuotesInsideElement = (element) => { const escapeDoubleQuotesInsideElement = (element) => {
const thisElement = element.replace(/"/g, '""'); const thisElement = element.replace(/"/g, '""');
return thisElement; return thisElement;
}; };
const appendElement = (element, lineLength, elementIdx, separator) => { const appendElement = (element, lineLength, elementIdx, separator) => {
const includesSpecials = checkSpecialCharsAndEmpty(element); const includesSpecials = checkSpecialCharsAndEmpty(element);
let thisElement = element; let thisElement = element;
if (includesSpecials) { if (includesSpecials) {
thisElement = escapeDoubleQuotesInsideElement(thisElement); thisElement = escapeDoubleQuotesInsideElement(thisElement);
} }
return ( return (
includesSpecials includesSpecials
? `"${thisElement}"${separatorOrLineBreak(lineLength, elementIdx, separator)}` ? `"${thisElement}"${separatorOrLineBreak(lineLength, elementIdx, separator)}`
: `${thisElement}${separatorOrLineBreak(lineLength, elementIdx, separator)}` : `${thisElement}${separatorOrLineBreak(lineLength, elementIdx, separator)}`
); );
}; };
const toCSV = data => convertArrayOfObjectsToCSV(data, {separator: ','}); const toCSV = data => convertArrayOfObjectsToCSV(data, {separator: ','});
const convertArrayOfObjectsToCSV = (data, { header, separator }) => { const convertArrayOfObjectsToCSV = (data, { header, separator }) => {
const array = [...data]; const array = [...data];
let csv = ''; let csv = '';
if (header) { if (header) {
header.forEach((headerEl, i) => { header.forEach((headerEl, i) => {
const thisHeaderEl = headerEl || (headerEl === 0 ? 0 : ''); const thisHeaderEl = headerEl || (headerEl === 0 ? 0 : '');
csv += appendElement(thisHeaderEl, header.length, i, separator); csv += appendElement(thisHeaderEl, header.length, i, separator);
}); });
} }
array.forEach((row, idx) => { array.forEach((row, idx) => {
const thisRow = Object.keys(row); const thisRow = Object.keys(row);
if (!header && idx === 0) { if (!header && idx === 0) {
thisRow.forEach((key, i) => { thisRow.forEach((key, i) => {
const value = key || (key === 0 ? 0 : ''); const value = key || (key === 0 ? 0 : '');
csv += appendElement(value, thisRow.length, i, separator); csv += appendElement(value, thisRow.length, i, separator);
}); });
} }
thisRow.forEach((key, i) => { thisRow.forEach((key, i) => {
const value = row[key] || (row[key] === 0 ? 0 : ''); const value = row[key] || (row[key] === 0 ? 0 : '');
csv += appendElement(value, thisRow.length, i, separator); csv += appendElement(value, thisRow.length, i, separator);
}); });
}); });
return csv; return csv;
}; };
\ No newline at end of file
const isEqual = function(original, fn) { const isEqual = function(original, fn) {
return function(val) { return function(val) {
fn(val===original); fn(val===original);
} }
}; };
const Store = function(cfg) { const Store = function(cfg) {
Observable.call(this); Observable.call(this);
this._props = cfg || {}; this._props = cfg || {};
}; };
Store.prototype = { Store.prototype = {
set: function(key, val) { set: function(key, val) {
if(this._props[key] !== val){ if(this._props[key] !== val){
this._props[ key ] = val; this._props[ key ] = val;
this.fire( 'change', key, val ); this.fire( 'change', key, val );
this.fire( key, val ); this.fire( key, val );
} }
}, },
get: function(key) { get: function(key) {
return this._props[key]; return this._props[key];
}, },
sub: function(key, fn) { sub: function(key, fn) {
if(Array.isArray(key)){ if(Array.isArray(key)){
var wrap = () => fn.apply(this, key.map(key=>this.get(key))); var wrap = () => fn.apply(this, key.map(key=>this.get(key)));
for( var i = 0, _i = key.length; i < _i; i++ ){ for( var i = 0, _i = key.length; i < _i; i++ ){
this.on(key[i], wrap) this.on(key[i], wrap)
} }
wrap(); wrap();
}else{ }else{
this.on( key, fn ) this.on( key, fn )
fn( this.get( key ) ); fn( this.get( key ) );
} }
return this; return this;
}, },
equal: function(key, val, fn) { equal: function(key, val, fn) {
const wrap = isEqual(val, fn); const wrap = isEqual(val, fn);
this.on(key, wrap); this.on(key, wrap);
wrap(this.get(key)); wrap(this.get(key));
return this; return this;
} }
}; };
Store.prototype = Object.assign(new Observable, Store.prototype); Store.prototype = Object.assign(new Observable, Store.prototype);
\ No newline at end of file
...@@ -3,64 +3,28 @@ ...@@ -3,64 +3,28 @@
<head> <head>
<title>Category mapper</title> <title>Category mapper</title>
<meta charset="utf-8" /> <meta charset="utf-8" />
<script src="controller/path.js"></script> <script src="core/Path.js"></script>
<script src="controller/require.js"></script> <script src="core/Require.js"></script>
<script src="livest-reloading.js"></script> <script src="core/LivestReloading.js"></script>
<script src="js/releasable-observer.js"></script> <script src="core/Observer.js"></script>
<script src="js/pcg-base.js"></script> <script src="core/DOM.js"></script>
<script src="js/pcg-dom-util.js"></script>
<script src="js/csv.js"></script> <script src="core/Random.Seeded.js"></script>
<script src="js/helpers/rand.js"></script>
<script src="js/model/store.js"></script> <script src="core/data/store/Store.js"></script>
<script src="js/model/data.js"></script> <script src="model/Store.js"></script>
<script src="js/model/provider.js"></script>
<script src="js/model/slice/productTable.js"></script>
<script src="js/model/tag.js"></script>
<script src="js/model/product.js"></script>
<script src="js/helpers/textFilter.js"></script>
<script src="js/helpers/answer.js"></script>
<script src="js/view/base.js"></script> <script src="js/view/base.js"></script>
<script src="js/view/cmp/base.js"></script>
<script src="js/view/cmp/menu.jsx"></script>
<script src="js/view/cmp/switch.js"></script>
<script src="js/view/cmp/table.js"></script>
<script src="js/view/cmp/tag.js"></script>
<script src="js/view/cmp/tagfield.js"></script>
<script src="js/view/page/products.js"></script>
<script src="js/view/page/export.jsx"></script>
<script src="js/view/page/generate.jsx"></script>
<script src="js/view/page/components.js"></script>
<script src="js/controller/exportLogic.js"></script>
<script src="js/controller/quizBits/main.js"></script>
<script src="js/controller/quizGenerator.js"></script>
<script src="js/controller/quizBits/checkbox.js"></script>
<script src="js/controller/quizBits/checkboxPhoto.js"></script>
<script src="js/controller/quizBits/radio.js"></script>
<script src="js/controller/quizBits/radioPhoto.js"></script>
$BUNDLE$ $BUNDLE$
<!--
<script src="../src/main.jsx"></script>
<link type="text/css" rel="stylesheet" href="src/main.css">-->
</head> </head>
<body> <body>
<script> <script>
initDataProvider($DATA$); define('start', ['main.jsx'], function(main) {
define('begin', ['main.jsx'], function(main) {
main.default(); main.default();
}); });
</script> </script>
......
import './main.scss'; import './main.scss';
import {Button} from '/view/cmp/button.jsx' import './view/page/login/Login.jsx';
import './view/page/account/Account.jsx';
import './view/cmp/switch/Switch.jsx';
export default function() { export default function() {
let tagField, exportEl; let tagField, exportEl;
div({ let dom = <div renderTo={document.body} cls="content">
renderTo: document.body, {view.cmp.Switch({cls: 'content-area', key: 'navigation.current'}, {
cls: 'content' login: new view.page.Login(),
}, main: new view.page.Account(),
div({ })}
cls: 'main-menu' </div>;
},
view.cmp.Menu({title: 'Продукты', id: 'products', key: 'mainMenuActive'}),
view.cmp.Menu({title: 'Ингредиенты', id: 'components', key: 'mainMenuActive'}),
view.cmp.Menu({title: 'Генерируем', id: 'generate', key: 'mainMenuActive'}),
view.cmp.Menu({title: 'Export', id: 'export', key: 'mainMenuActive', cls: 'menu-item-export'}),
),
view.cmp.Switch({cls: 'content-area', key: 'mainMenuActive'}, {
products: new view.page.Products(),
components: new view.page.Components(),
'export': <view.page.Export/>,
generate: new view.page.Generate()
})
);
}; };
const store = new Store({ const store = new Store({
mainMenuActive: 'products', 'navigation.current': 'login'
exportFormat: 'yaml', });
exportData: 'tags',
exportTableName: 'SOMETABLE',
productFilterText: '',
componentFilterText: '', try{
'productFilterByTitle': true, var data = JSON.parse( localStorage.getItem( 'store' ) );
'productFilterByComponent': true, window.addEventListener && console.log(data)
'productFilterByTag': true, for(var k in data){
'generateType': 'radio', store.set(k, data[k]);
'generatePhoto': false, }
generateCategory: 'product' }catch( e ){}
}); store.on('change', function() {
localStorage.setItem('store', JSON.stringify(store._props));
});
try{
var data = JSON.parse( localStorage.getItem( 'store' ) );
window.addEventListener && console.log(data)
for(var k in data){
store.set(k, data[k]);
}
}catch( e ){}
store.on('change', function() {
localStorage.setItem('store', JSON.stringify(store._props));
});
import './button.scss'; import './button.scss';
export const Button = D.declare('cmp.Button', function(cfg) { export const Button = D.declare('view.cmp.Button', function(cfg) {
return <input type='text' {...cfg} cls='button'/> return <input type='text' {...cfg} cls='button'/>
}); });
\ No newline at end of file
import './LabeledField.scss';
export default D.declare('view.cmp.field.LabeledField', (cfg)=>
<div class="labeled-field">
<label><span class="labeled-field__label">{cfg.label}</span>
<input class="labeled-field__input" type={'text'}/>
</label>
</div>
)
\ No newline at end of file
.labeled-field {
display: flex;
}
.labeled-field__label {
width: 150px;
}
\ No newline at end of file
view.cmp.Table = function(cfg){ const Table = function(cfg){
Object.assign(this, cfg); Object.assign(this, cfg);
const children = this.childrenEl = D.div({ const children = this.childrenEl = D.div({
cls: 'cmp-table__children' cls: 'cmp-table__children'
}); });
this.dom = D.div({ this.dom = D.div({
cls: D.cls('cmp-table', cfg.cls) cls: D.cls('cmp-table', cfg.cls)
}, children); }, children);
this.currentSlice = []; this.currentSlice = [];
this.childrenEls = []; this.childrenEls = [];
setTimeout(()=>this.updateChildren(),0); setTimeout(()=>this.updateChildren(),0);
}; };
view.cmp.Table.prototype = { Table.prototype = {
currentSlice: [], currentSlice: [],
childrenEls: [], childrenEls: [],
childrenEl: null, childrenEl: null,
filterText: '', filterText: '',
filterFn: () => true, filterFn: () => true,
afterFilter: ()=>true, afterFilter: ()=>true,
filter: function(text) { filter: function(text) {
this.filterText = text; this.filterText = text;
this.filterRegExp = new RegExp(text.replace(/[\[\]\(\)\?\*\.\+]/g,'').replace(/\s+/g,'|'), 'ig') this.filterRegExp = new RegExp(text.replace(/[\[\]\(\)\?\*\.\+]/g,'').replace(/\s+/g,'|'), 'ig')
}, },
fetch: function() { fetch: function() {
const field = Object.keys(this.sort[0])[0] const field = Object.keys(this.sort[0])[0]
this.currentSlice = this.items this.currentSlice = this.items
.filter(item=>this.filterFn.call(item, this.filterText, this)) .filter(item=>this.filterFn.call(item, this.filterText, this))
.sort((a,b)=>a[field] > b.field ? 1 : a[field] < b.field ? -1 : 0) .sort((a,b)=>a[field] > b.field ? 1 : a[field] < b.field ? -1 : 0)
.map(data=>({data, dom: null})); .map(data=>({data, dom: null}));
this.afterFilter(this.getSelected()); this.afterFilter(this.getSelected());
}, },
updateChildren: function() { updateChildren: function() {
this.fetch(); this.fetch();
D.removeChildren(this.childrenEl); D.removeChildren(this.childrenEl);
this.currentSlice.forEach((item) => { this.currentSlice.forEach((item) => {
D.appendChild(this.childrenEl, item.dom = this.itemTpl(item.data, this, item)); D.appendChild(this.childrenEl, item.dom = this.itemTpl(item.data, this, item));
}) })
}, },
updateChild: function(item) { updateChild: function(item) {
if(item.dom){ if(item.dom){
const dom = this.itemTpl(item.data, this, item); const dom = this.itemTpl(item.data, this, item);
item.dom.parentNode.replaceChild(dom, item.dom); item.dom.parentNode.replaceChild(dom, item.dom);
item.dom = dom; item.dom = dom;
} }
}, },
updateChildByData: function(data) { updateChildByData: function(data) {
const match = this.currentSlice.filter(i=>i.data===data), const match = this.currentSlice.filter(i=>i.data===data),
item = match[0]; item = match[0];
if(!item) if(!item)
return; return;
if(item.dom){ if(item.dom){
const dom = this.itemTpl(item.data, this, item); const dom = this.itemTpl(item.data, this, item);
item.dom.parentNode.replaceChild(dom, item.dom); item.dom.parentNode.replaceChild(dom, item.dom);
item.dom = dom; item.dom = dom;
} }
}, },
getSelected: function() { getSelected: function() {
return this.currentSlice.filter(i=>!i.hidden).map(i=>i.data) return this.currentSlice.filter(i=>!i.hidden).map(i=>i.data)
}, },
highlight: function(text) { highlight: function(text) {
if(!this.filterText) if(!this.filterText)
return text; return text;
const txts = []; const txts = [];
; ;
return D.join(text.replace(this.filterRegExp, function(match) { return D.join(text.replace(this.filterRegExp, function(match) {
txts.push(match); txts.push(match);
return '|||||' return '|||||'
}).split('|||||'), (i)=>D.span({cls: 'highlighted'}, txts[i])) }).split('|||||'), (i)=>D.span({cls: 'highlighted'}, txts[i]))
}, },
getActionDelegate: function() { getActionDelegate: function() {
var me = this; var me = this;
return function(e) { return function(e) {
const [type, val] = this.getAttribute('data-action').split(':'); const [type, val] = this.getAttribute('data-action').split(':');
const match = me.currentSlice.filter(({data})=>data.id+'' === val+''); const match = me.currentSlice.filter(({data})=>data.id+'' === val+'');
if(type === 'hide'){ if(type === 'hide'){
if(match.length){ if(match.length){
match[0].hidden = true; match[0].hidden = true;
me.updateChild(match[0]) me.updateChild(match[0])
me.afterFilter(me.getSelected()); me.afterFilter(me.getSelected());
} }
} }
if(type === 'toggle'){ if(type === 'toggle'){
if(match.length){ if(match.length){
match[0].full = !match[0].full; match[0].full = !match[0].full;
me.updateChild(match[0]) me.updateChild(match[0])
} }
} }
e.stopPropagation(); e.stopPropagation();
} }
}, },
}; };
\ No newline at end of file
export default D.declare('view.cmp.List', Table);
view.cmp.Switch = (cfg, contentHash) => { export default D.declare('view.cmp.Switch', (cfg, contentHash) => {
const cmp = div( { const cmp = div( {
cls: update => store.sub( cls: update => store.sub(
cfg.key, cfg.key,
val => update( val => update(
D.cls( D.cls(
'cmp-switch', 'cmp-switch',
cfg.cls, cfg.cls,
{ 'cmp-switch__filled': val in contentHash } ) ) ) } ); { 'cmp-switch__filled': val in contentHash } ) ) ) } );
store.sub( cfg.key, (val)=> { store.sub( cfg.key, (val)=> {
D.removeChildren(cmp); D.removeChildren(cmp);
if(val in contentHash) if(val in contentHash)
D.appendChild( cmp, contentHash[ val ] ); D.appendChild( cmp, contentHash[ val ] );
}); });
return cmp; return cmp;
}; });
\ No newline at end of file \ No newline at end of file
D.declare('view.cmp.Tag', function(text) { D.declare('view.cmp.Tag', function(text) {
return D.span({cls: 'tag'}, '#', text) return D.span({cls: 'tag'}, '#', text)
}); });
\ No newline at end of file
view.cmp.TagField = (function(){ export default D.declare('view.cmp.TagField', (function(){
const TagField = function( data ){ const TagField = function( data ){
Observable.call( this ); Observable.call( this );
var field, isFocused = false, lastVal, dropDown; var field, isFocused = false, lastVal, dropDown;
//var cacheTagsDom = {}; //var cacheTagsDom = {};
var offset = 0; var offset = 0;
var matchedItems = 0; var matchedItems = 0;
var neBlur = false; var neBlur = false;
var autocomplete = ( e ) => { var autocomplete = ( e ) => {
offset = 0; offset = 0;
var val = field.value.trim().toLowerCase(); var val = field.value.trim().toLowerCase();
if( lastVal !== val ){ if( lastVal !== val ){
var matched = val === '' ? [] : dP.tags.filter( t => t.name.toLowerCase().trim() !== val && t.name.toLowerCase().trim().indexOf( val ) > -1 ).slice( 0, 20 ); var matched = val === '' ? [] : dP.tags.filter( t => t.name.toLowerCase().trim() !== val && t.name.toLowerCase().trim().indexOf( val ) > -1 ).slice( 0, 20 );
this.value = lastVal = val; this.value = lastVal = val;
matchedItems = matched.length; matchedItems = matched.length;
D.removeChildren( dropDown ); D.removeChildren( dropDown );
matched.forEach( function( match ){ matched.forEach( function( match ){
D.appendChild( dropDown, div( { D.appendChild( dropDown, div( {
cls: 'cmp-tag-dropdown-item', cls: 'cmp-tag-dropdown-item',
on: { on: {
click: () => { click: () => {
field.value = match.name; field.value = match.name;
autocomplete(); autocomplete();
}, },
mousedown: () => { mousedown: () => {
neBlur = true; neBlur = true;
setTimeout( () => neBlur = false, 40 ) setTimeout( () => neBlur = false, 40 )
clearTimeout( blurTimeout ) clearTimeout( blurTimeout )
} }
} }
}, },
view.cmp.Table.prototype.highlight.call( { view.cmp.Table.prototype.highlight.call( {
filterText: val, filterText: val,
filterRegExp: new RegExp( val, 'ig' ) filterRegExp: new RegExp( val, 'ig' )
}, match.name ) ) );//cacheTagsDom[match.id] || (cacheTagsDom[match.id] = ); }, match.name ) ) );//cacheTagsDom[match.id] || (cacheTagsDom[match.id] = );
} ); } );
dropDownUpdateCls(); dropDownUpdateCls();
} }
}; };
var _dropDownUpdateCls, var _dropDownUpdateCls,
dropDownUpdateCls = function(){ dropDownUpdateCls = function(){
_dropDownUpdateCls( _dropDownUpdateCls(
D.cls( 'cmp-tag-dropdown', { D.cls( 'cmp-tag-dropdown', {
'cmp-tag-dropdown__hidden': matchedItems === 0 || isFocused === false 'cmp-tag-dropdown__hidden': matchedItems === 0 || isFocused === false
} ) } )
) )
}; };
var blurTimeout; var blurTimeout;
var el = div( { cls: 'cmp-tag-field' }, var el = div( { cls: 'cmp-tag-field' },
field = D.input( { field = D.input( {
cls: 'cmp-tag-input', cls: 'cmp-tag-input',
attr: { placeholder: 'Тэг' }, attr: { placeholder: 'Тэг' },
on: { on: {
input: autocomplete, input: autocomplete,
focus: () => { focus: () => {
clearTimeout( blurTimeout ); clearTimeout( blurTimeout );
isFocused = true; isFocused = true;
dropDownUpdateCls() dropDownUpdateCls()
}, },
blur: () => { blur: () => {
if( !neBlur ){ if( !neBlur ){
blurTimeout = setTimeout( function(){ blurTimeout = setTimeout( function(){
isFocused = false; isFocused = false;
dropDownUpdateCls(); dropDownUpdateCls();
}, 100 ) }, 100 )
} }
} }
} }
} ), } ),
dropDown = div( { dropDown = div( {
cls: ( update ) => { cls: ( update ) => {
_dropDownUpdateCls = update; _dropDownUpdateCls = update;
dropDownUpdateCls(); dropDownUpdateCls();
} }
} ) } )
); );
this.dom = el; this.dom = el;
}; };
TagField.prototype = { TagField.prototype = {
getValue: function(){ getValue: function(){
return this.value; return this.value;
} }
}; };
TagField.prototype = Object.assign( new Observable, TagField.prototype ); TagField.prototype = Object.assign( new Observable, TagField.prototype );
return TagField; return TagField;
})(); })());
\ No newline at end of file \ No newline at end of file
export default D.declare('view.page.Account', ()=>
<div>
Account Page
</div>
)
\ No newline at end of file
import '../../cmp/field/LabeledField.jsx'
export default D.declare('view.page.Login', ()=>
<div>
Login page
<view.cmp.field.LabeledField label={'Login'}/>
<view.cmp.field.LabeledField label={'Password'}/>
</div>
)
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment