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

New question type: Product contains 4 components

parent a85a4609
Pipeline #510 failed with stage
......@@ -20,6 +20,7 @@
<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>
......@@ -33,9 +34,11 @@
<script src="js/view/page/products.js"></script>
<script src="js/view/page/export.js"></script>
<script src="js/view/page/generate.js"></script>
<script src="js/view/page/components.js"></script>
<script src="js/controller/exportLogic.js"></script>
<script src="js/controller/quizGenerator.js"></script>
<script src="js/controller/quizBits/main.js"></script>
......
const qB = {
randomProduct: function({
minComponents,
amount,
single,
connectedByTags,
minSimilarTags
}, log) {
let filtered = Object.values(dP.products);
filtered = filtered
.filter((p)=>p.getComponents().length >= minComponents);
log.push(`Have >= ${minComponents} components: `+filtered.length);
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.values(similarTags)
.filter( a => a.length >= minAmount);
log.push('Similar clustered components by tags: '+similarClusters.title);
filtered = rand(similarClusters);
}
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, rand(minAmount, maxAmount));
log.push('Base products:');
result.forEach(p => log.push(` > ${p.title}`))
log.push('');
}
return result;
},
getComponents: function(product, ) {
}
};
\ No newline at end of file
const lapk = (text)=>`„${text}“`;
const rand = function(a, b){
if(Array.isArray(a)){
return a[Math.random()*a.length|0];
......@@ -35,14 +36,64 @@ const quizTypes = {
checkboxPhoto: {
},
radio: [{
radio: [
{
questionCmpAmount: 4,
probability: 10,
type: 'Product contains 4 components',
from(log){
let products = qB.randomProduct({
minComponents: this.questionCmpAmount,
amount: {from: 4, to: 8},
connectedByTags: true,
minSimilarTags: 2
}, log);
for( let i = 0, _i = products.length; i < _i; i++ ){
const product = products[ i ],
cmps = product
.getComponents()
.filter(cmp => products.filter(p=>p.containsComponent(cmp)).length === 1)
if(cmps.length>=this.questionCmpAmount){
log.push(`Product ${lapk(product.title)} have >= ${this.questionCmpAmount} uniq components (${cmps.length}):`);
const shuffled = shuffle(cmps.slice()).slice(0, this.questionCmpAmount);
cmps.forEach(c=>log.push(`${shuffled.indexOf(c)>-1?'+':' '} > ${c.name}`));
let wrong = products.slice();
wrong.splice(i,1);
return {
correct: product,
wrong: wrong,
uniq: shuffled
}
}
}
shuffle(products);
console.log(products.map(p=>p.getComponents()))
},
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(c=>new Answer.Wrong(textFormat(c.title)))))
}
},
{
probability: 10,
answers: {from: 3, to: 6},
question: (product)=> `Выберите лишний ингредиент, НЕ входящий в продукт "${product.title}"`,
type: 'Do not contain',
from: ()=>
rand(
Object.keys(dP.products)
.map(k=>dP.products[k])
.filter((p)=>dP.Product.getComponents(p).length > 1)
),
from: (log)=>qB.randomProduct({ minComponents: 2, single: true }, log),
answer( product, log ){
......@@ -58,7 +109,6 @@ const quizTypes = {
donors = [];
log.push('Base product: '+product.title);
log.push(`Have ${dP.Product.getComponents(product).length} components. Took ${out.length}:`);
out.forEach(function(a) {
log.push(' > '+ a.text)
......@@ -149,13 +199,7 @@ const quizTypes = {
console.log(dP.Product.getTags(product), donors)
return out;
},
answers: {from: 3, to: 6},
question: (product)=> `Выберите лишний ингредиент, НЕ входящий в продукт "${product.title}"`,
probability: 10
}
}],
radioPhoto: {
......@@ -168,13 +212,20 @@ const shuffle = function (a) {
}
return a;
}
const quizGenerator = function(type, photo) {
const quizGenerator = function(type, photo, subType) {
const types = quizTypes[type+(photo?'Photo':'')];
const cfg = probabilityRand(types);
const log = ['Quiz generate '+type+(photo ? ' with photo':'') +' '+(cfg?`${lapk(cfg.type)}`:'. FAIL')];
const cfg = probabilityRand(quizTypes[type+(photo?'Photo':'')]);
const log = ['Quiz generate '+type+(photo ? ' with photo':'') +' '+(cfg?`„${cfg.type}“`:'. FAIL')];
if(!cfg)
return {answers: [], log};
const source = cfg.from.call(cfg, log),
const source = cfg.from.call(cfg, log);
if(source === false){
// давай по новой
return false;
}
answers = shuffle(cfg.answer.call(cfg, source, log));
if(answers === false){
// давай по новой
......
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
......@@ -19,9 +19,13 @@ const textFilterMatched = function(where, what) {
return yep;
};
const textFormat = function(text) {
const textFormat = function(text, html) {
if(typeof text !== 'string')
return text;
text = text.trim();
if(text[0] === ',')text = text.substr(1);
if(text[text.length-1] === ',')text = text.substr(0, text.length - 1);
const income = text;
if(text.split(/[«"»]/).length % 2 === 0){
// odd count of quotes
......@@ -45,7 +49,7 @@ const textFormat = function(text) {
.replace(/(\d)(руб)/g,'$1 $2')
//.replace(/([а-яА-Я])[—-]([а-яА-Я])/g, (full, a, b)=>add(a+'-'+b))
.replace(/([а-яА-Я])[—-]([а-яА-Я])/g, (full, a, b)=>add('<nobr>'+a+'-'+b+'</nobr>'))
.replace(/([а-яА-Я])[—-]([а-яА-Я])/g, (full, a, b)=>add(html?'<nobr>'+a+'-'+b+'</nobr>': a+'-'+b))
.replace(/([0-9])[,]([0-9])/g, (full, a, b)=>add(a+','+b))
......@@ -75,16 +79,18 @@ const textFormat = function(text) {
//.replace(/\. /g,'.&shy; ')
// phone
.replace(/(\+7|8)[\s\(]*(\d{3})[\s\)]*(\d{3})[\s\-—–]*(\d{2})[\s-—–]*(\d{2})/, '+7&nbsp;($2)&nbsp;$3-$4-$5')
.replace(/(\+7|8)[\s\(]*(\d{3})[\s\)]*(\d{3})[\s\-—–]*(\d{2})[\s-—–]*(\d{2})/, html ?'+7&nbsp;($2)&nbsp;$3-$4-$5' : '+7 ($2) $3-$4-$5')
.replace(/свершении/g,'совершении')
.replace(/[А-Я]+/g, function(text) {
[html?'replace':'trim'](/[А-Я]+/g, function(text) {
return text.length > 2 || text === 'НЕ' ? '<span class="important">'+text.toLowerCase()+'</span>': text;
})
.replace(/свершении/g,'совершении')
.replace(/, ([А-Я])/g, (f, a)=>', '+a.toLowerCase())
.replace(/\|\|\|\|\|/g,'\n\n')
.replace(/\*\*([^*]+)\*\*/g,'<span class="notificate-text">$1</span>')
[html?'replace':'trim'](/\*\*([^*]+)\*\*/g,'<span class="notificate-text">$1</span>')
;
......@@ -147,7 +153,7 @@ const textFormat = function(text) {
let out = remake.length === 1 ? remake[0] : remake.map(a=>'<p>'+a+'</p>').join('\n');
let out = remake.length === 1 ? remake[0] : remake.map(a=>html?'<p>'+a+'</p>':a).join('\n');
out = out.replace(/_!@#(\d+)#@!_/g, (f, num)=>{
return valids[f]
});
......
......@@ -4,6 +4,7 @@ const store = new Store({
exportData: 'tags',
exportTableName: 'SOMETABLE',
productFilterText: '',
componentFilterText: '',
'productFilterByTitle': true,
'productFilterByComponent': true,
'productFilterByTag': true
......
......@@ -7,5 +7,24 @@ dP.Product = {
},
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
......@@ -10,12 +10,21 @@ const dataProvider = {
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: {}
});
let key;
for(key in data.components){
const cmp = data.components[key];
......@@ -30,10 +39,7 @@ const dataProvider = {
return store
}, {});
for(key in data.products){
let product = data.products[key];
product.tags = [];
}
data.connections.forEach((connection) => {
dP.maxConnection = Math.max(dP.maxConnection, connection.cid);
......
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
......@@ -25,7 +25,7 @@ const init = function() {
),
view.cmp.Switch({cls: 'content-area', key: 'mainMenuActive'}, {
products: new view.page.Products(),
components: div({}, 'components'),
components: new view.page.Components(),
'export': new view.page.Export(),
generate: new view.page.Generate()
})
......
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