diff --git a/public/wcif-extensions/CompetitionConfig.json b/public/wcif-extensions/CompetitionConfig.json index 96019e6..118dc16 100644 --- a/public/wcif-extensions/CompetitionConfig.json +++ b/public/wcif-extensions/CompetitionConfig.json @@ -62,6 +62,10 @@ "printScrambleCheckerForBlankScorecards": { "description": "A flag indicating whether the box for scrambler checker signature should be printed for blank scorecards.", "type": "boolean" + }, + "printDedicatedMultiBlindScorecards": { + "description": "A flag indicating whether special Multi-Blind scorecards should be printed", + "type": "boolean" } }, "required": ["localNamesFirst", "printOneName", "scorecardsBackgroundUrl", "competitorsSortingRule", "noTasksForNewcomers", "tasksForOwnEventsOnly"] diff --git a/src/components/Competition/ConfigManager/GeneralConfig/GeneralConfig.js b/src/components/Competition/ConfigManager/GeneralConfig/GeneralConfig.js index fd033ec..f3e3bca 100644 --- a/src/components/Competition/ConfigManager/GeneralConfig/GeneralConfig.js +++ b/src/components/Competition/ConfigManager/GeneralConfig/GeneralConfig.js @@ -107,6 +107,7 @@ const GeneralConfig = ({ wcif, onWcifChange }) => { printScrambleCheckerForTopRankedCompetitors, printScrambleCheckerForFinalRounds, printScrambleCheckerForBlankScorecards, + printDedicatedMultiBlindScorecards, } = getExtensionData('CompetitionConfig', wcif); return ( @@ -299,6 +300,18 @@ const GeneralConfig = ({ wcif, onWcifChange }) => { label="Print out scrambler checker sign box for blank scorecards" /> + + + } + label="Print out dedicated Multi-Blind scorecards" + /> + { const [tabValue, setTabValue] = useState(0); + const { printDedicatedMultiBlindScorecards } = getExtensionData( + 'CompetitionConfig', + wcif + ); + const roundsMissingAssignmentsNames = roundsMissingAssignments( wcif ).map(round => activityCodeToName(round.id)); @@ -49,11 +55,30 @@ const PrintingManager = ({ wcif }) => { setTabValue(value)}> + {printDedicatedMultiBlindScorecards && ( + + )} - {tabValue === 0 && } + {tabValue === 0 && ( + + )} {tabValue === 1 && } + {printDedicatedMultiBlindScorecards && tabValue === 2 && ( + + )} - - - + {!multiBlindOnly && ( + + + + )} ); diff --git a/src/logic/documents/scorecards.js b/src/logic/documents/scorecards.js index d40373a..71d30a6 100644 --- a/src/logic/documents/scorecards.js +++ b/src/logic/documents/scorecards.js @@ -48,7 +48,13 @@ const scorecardPaperSizeInfos = { const maxAttemptCountByFormat = { '1': 1, '2': 2, '3': 3, m: 3, a: 5 }; -export const downloadScorecards = (wcif, rounds, rooms, language) => { +export const downloadScorecards = ( + wcif, + rounds, + rooms, + language, + orientation +) => { const { scorecardsBackgroundUrl, scorecardPaperSize } = getExtensionData( 'CompetitionConfig', wcif @@ -57,13 +63,14 @@ export const downloadScorecards = (wcif, rounds, rooms, language) => { const pdfDefinition = scorecardsPdfDefinition( scorecards(wcif, rounds, rooms, language), imageData, - scorecardPaperSize + scorecardPaperSize, + orientation ); pdfMake.createPdf(pdfDefinition).download(`${wcif.id}-scorecards.pdf`); }); }; -export const downloadBlankScorecards = (wcif, language) => { +export const downloadBlankScorecards = (wcif, language, orientation) => { const { scorecardsBackgroundUrl, scorecardPaperSize } = getExtensionData( 'CompetitionConfig', wcif @@ -72,7 +79,8 @@ export const downloadBlankScorecards = (wcif, language) => { const pdfDefinition = scorecardsPdfDefinition( blankScorecards(wcif, language), imageData, - scorecardPaperSize + scorecardPaperSize, + orientation ); pdfMake .createPdf(pdfDefinition) @@ -83,7 +91,8 @@ export const downloadBlankScorecards = (wcif, language) => { const scorecardsPdfDefinition = ( scorecardList, imageData, - scorecardPaperSize + scorecardPaperSize, + pageOrientation = 'horizontal' ) => { const { pageWidth, @@ -100,7 +109,16 @@ const scorecardsPdfDefinition = ( { x: 60, y: 590 }, { x: 360, y: 590 }, ].slice(0, scorecardsPerPage); - const cutLines = + + let actualPageWidth = pageWidth; + let actualPageHeight = pageHeight; + + if (pageOrientation === 'landscape') { + actualPageWidth = pageHeight; + actualPageHeight = pageWidth; + } + + const horizontalCutlines = scorecardsPerPage === 4 ? { canvas: [ @@ -119,7 +137,28 @@ const scorecardsPdfDefinition = ( ], } : {}; + const verticalCutlines = + scorecardsPerPage === 4 + ? { + canvas: [ + cutLine({ + y1: horizontalMargin, + x1: pageHeight / 2, + y2: pageWidth - horizontalMargin, + x2: pageHeight / 2, + }), + cutLine({ + y1: pageWidth / 2, + x1: verticalMargin, + y2: pageWidth / 2, + x2: pageHeight - verticalMargin, + }), + ], + } + : {}; + const cutLines = + pageOrientation === 'horizontal' ? horizontalCutlines : verticalCutlines; return { background: [ ...(imageData === null @@ -132,8 +171,9 @@ const scorecardsPdfDefinition = ( }))), cutLines, ], - pageSize: { width: pageWidth, height: pageHeight }, + pageSize: { width: actualPageWidth, height: actualPageHeight }, pageMargins: [horizontalMargin, verticalMargin], + pageOrientation: pageOrientation, content: { layout: { /* Outer margin is done using pageMargins, we use padding for the remaining inner margins. */ @@ -149,7 +189,7 @@ const scorecardsPdfDefinition = ( }, table: { widths: Array(scorecardsPerRow).fill('*'), - heights: pageHeight / scorecardsPerRow - 2 * verticalMargin, + heights: actualPageHeight / scorecardsPerRow - 2 * verticalMargin, dontBreakRows: true, body: chunk(scorecardList, scorecardsPerRow), }, @@ -173,6 +213,7 @@ const scorecards = (wcif, rounds, rooms, language) => { scorecardPaperSize, scorecardOrder, printScorecardsCoverSheets, + printDedicatedMultiBlindScorecards, } = getExtensionData('CompetitionConfig', wcif); const { scorecardsPerPage } = scorecardPaperSizeInfos[scorecardPaperSize]; let cards = flatMap(rounds, round => { @@ -225,6 +266,7 @@ const scorecards = (wcif, rounds, rooms, language) => { round, wcif ), + printDedicatedMultiBlindScorecards, }) ); if (groupCoverSheet) { @@ -351,6 +393,7 @@ const blankScorecards = (wcif, language) => { printStations, scorecardPaperSize, printScrambleCheckerForBlankScorecards, + printDedicatedMultiBlindScorecards, } = getExtensionData('CompetitionConfig', wcif); const { scorecardsPerPage } = scorecardPaperSizeInfos[scorecardPaperSize]; return flatMap(uniq(attemptCounts), attemptCount => @@ -362,6 +405,7 @@ const blankScorecards = (wcif, language) => { scorecardPaperSize, language: language, printScrambleCheckerBox: printScrambleCheckerForBlankScorecards, + printDedicatedMultiBlindScorecards, }) ) ); @@ -382,6 +426,7 @@ const scorecard = ({ featured = false, language = 'en', printScrambleCheckerBox, + printDedicatedMultiBlindScorecards = false, }) => { const defaultTranslationData = translation('en'); const translationData = translation(language); @@ -391,7 +436,6 @@ const scorecard = ({ ([data1, data2], key) => [data1[key], data2[key]], [translationData, defaultTranslationData] ); - return phrase || defaultPhrase; }; @@ -406,7 +450,12 @@ const scorecard = ({ } = scorecardPaperSizeInfos[scorecardPaperSize]; const scorecardWidth = pageWidth / scorecardsPerRow - 2 * horizontalMargin; - return [ + const isDedicatedMultiBlindScorecard = + printDedicatedMultiBlindScorecards && + eventId && + eventId.startsWith('333mbf'); + + const baseHeader = [ { fontSize: 10, columns: [ @@ -417,7 +466,7 @@ const scorecard = ({ featured ? { text: '★', - font: 'WenQuanYiZenHei', // Roboto (default) does not support unicode icons like ★ + font: 'WenQuanYiZenHei', alignment: 'right', } : {}, @@ -430,133 +479,194 @@ const scorecard = ({ margin: [0, 0, 0, 10], alignment: 'center', }, - { - margin: [25, 0, 0, 0], - table: { - widths: ['*', 30, 30, ...(printStations ? [30] : [])], - body: [ - columnLabels([ - t('eventLabel'), - { text: t('round'), alignment: 'center' }, - { text: t('group'), alignment: 'center' }, - ...(printStations - ? [{ text: t('station'), alignment: 'center' }] - : []), - ]), - [ - eventId ? t('eventName', eventId) : ' ', - { text: roundNumber, alignment: 'center' }, - { text: groupNumber, alignment: 'center' }, - ...(printStations - ? [{ text: stationNumber, alignment: 'center' }] - : []), - ], + ]; + + const eventRoundTable = { + margin: [25, 0, 0, 0], + table: { + widths: ['*', 30, 30, ...(printStations ? [30] : [])], + body: [ + columnLabels([ + t('eventLabel'), + { text: t('round'), alignment: 'center' }, + { text: t('group'), alignment: 'center' }, + ...(printStations + ? [{ text: t('station'), alignment: 'center' }] + : []), + ]), + [ + eventId ? t('eventName', eventId) : ' ', + { text: roundNumber, alignment: 'center' }, + { text: groupNumber, alignment: 'center' }, + ...(printStations + ? [{ text: stationNumber, alignment: 'center' }] + : []), ], - }, + ], }, - { - margin: [25, 0, 0, 0], - table: { - widths: [30, '*'], - body: [ - columnLabels([ - 'ID', - [ - { text: t('name'), alignment: 'left', width: 'auto' }, - { - text: - competitor.wcaId || - // If the competitor has a name, then this is a new competitor - // Else this is a blank scorecard - (competitor.name ? t('newCompetitor') : ' '), - alignment: 'right', - }, - ], - ]), + }; + + const competitorTable = { + margin: [25, 0, 0, 0], + table: { + widths: [30, '*'], + body: [ + columnLabels([ + 'ID', [ - { text: competitor.registrantId || ' ', alignment: 'center' }, + { text: t('name'), alignment: 'left', width: 'auto' }, { - text: pdfName(competitor.name || ' ', { - swapLatinWithLocalNames: localNamesFirst, - short: printOneName, - }), - maxHeight: 20 /* See: https://github.com/bpampuch/pdfmake/issues/264#issuecomment-108347567 */, + text: + competitor.wcaId || + (competitor.name ? t('newCompetitor') : ' '), + alignment: 'right', }, ], + ]), + [ + { text: competitor.registrantId || ' ', alignment: 'center' }, + { + text: pdfName(competitor.name || ' ', { + swapLatinWithLocalNames: localNamesFirst, + short: printOneName, + }), + maxHeight: 20, + }, ], - }, + ], }, - { - margin: [0, 10, 0, 0], - table: { - widths: [ - 16, - 25, - ...(printScrambleCheckerBox ? [25] : []), - '*', - 25, - 25, - ] /* Note: 16 (width) + 4 + 4 (defult left and right padding) + 1 (left border) = 25 */, - body: [ - columnLabels( - [ - '', - t('scr'), - ...(printScrambleCheckerBox ? [t('check')] : []), - t('result'), - t('judge'), - t('comp'), - ], - { - alignment: 'center', - } - ), - ...attemptRows( - cutoff, - attemptCount, - scorecardWidth, - printScrambleCheckerBox - ), + }; + + const multiBlindTable = { + margin: [0, 10, 0, 0], + table: { + widths: [ + 16, + 25, + 25, + ...(printScrambleCheckerBox ? [25] : []), + 25, + 25, + '*', + 25, + 25, + ], + body: [ + columnLabels( [ - { - text: t('extra') + ' (' + t('delegateInitials') + ' _______)', - ...noBorder, - colSpan: 5 + printScrambleCheckerBox, - margin: [0, 1], - fontSize: 10, - }, + '', + t('del'), + t('scr'), + ...(printScrambleCheckerBox ? [t('check')] : []), + t('solved'), + t('declared'), + t('time'), + t('judge'), + t('comp'), ], - attemptRow('_', printScrambleCheckerBox), + { alignment: 'center' } + ), + ...Array.from({ length: attemptCount }).map((_, i) => + multiBlindAttemptRow(i + 1, printScrambleCheckerBox) + ), + [ + { + text: t('extra') + ' (' + t('delegateInitials') + ' _______)', + ...noBorder, + colSpan: 8 + (printScrambleCheckerBox ? 1 : 0), + margin: [0, 1], + fontSize: 10, + }, + ...Array(printScrambleCheckerBox ? 1 : 0).fill(''), + ], + multiBlindAttemptRow('_', printScrambleCheckerBox), + [ + { + text: '', + ...noBorder, + colSpan: 8 + (printScrambleCheckerBox ? 1 : 0), + margin: [0, 1], + }, + ...Array(printScrambleCheckerBox ? 1 : 0).fill(''), + ], + ], + }, + }; + + const standardAttemptTable = { + margin: [0, 10, 0, 0], + table: { + widths: [16, 25, ...(printScrambleCheckerBox ? [25] : []), '*', 25, 25], + body: [ + columnLabels( [ - { - text: '', - ...noBorder, - colSpan: 5 + printScrambleCheckerBox, - margin: [0, 1], - }, + '', + t('scr'), + ...(printScrambleCheckerBox ? [t('check')] : []), + t('result'), + t('judge'), + t('comp'), ], + { + alignment: 'center', + } + ), + ...attemptRows( + cutoff, + attemptCount, + scorecardWidth, + printScrambleCheckerBox + ), + [ + { + text: t('extra') + ' (' + t('delegateInitials') + ' _______)', + ...noBorder, + colSpan: 5 + (printScrambleCheckerBox ? 1 : 0), + margin: [0, 1], + fontSize: 10, + }, + ...Array(printScrambleCheckerBox ? 1 : 0).fill(''), + ], + attemptRow('_', printScrambleCheckerBox), + [ + { + text: '', + ...noBorder, + colSpan: 5 + (printScrambleCheckerBox ? 1 : 0), + margin: [0, 1], + }, + ...Array(printScrambleCheckerBox ? 1 : 0).fill(''), ], - }, - }, - { - fontSize: 10, - columns: [ - cutoff - ? { - text: `${t('cutoff')}: ${cutoffToString(cutoff, eventId)}`, - alignment: 'center', - } - : {}, - timeLimit - ? { - text: `${t('timeLimit')}: ${timeLimitToString(timeLimit, { - totalText: t('total'), - })}`, - alignment: 'center', - } - : {}, ], }, + }; + + const limitsRow = { + fontSize: 10, + columns: [ + cutoff + ? { + text: `${t('cutoff')}: ${cutoffToString(cutoff, eventId)}`, + alignment: 'center', + } + : {}, + timeLimit + ? { + text: `${t('timeLimit')}: ${timeLimitToString(timeLimit, { + totalText: t('total'), + })}`, + alignment: 'center', + } + : {}, + ], + }; + + return [ + ...baseHeader, + eventRoundTable, + competitorTable, + isDedicatedMultiBlindScorecard ? multiBlindTable : standardAttemptTable, + limitsRow, ]; }; @@ -729,4 +839,22 @@ const attemptRow = (attemptNumber, needsScrambleChecker) => [ {}, ]; +const multiBlindAttemptRow = (attemptNumber, needsScrambleChecker) => [ + { + text: attemptNumber, + ...noBorder, + fontSize: 20, + bold: true, + alignment: 'center', + }, + {}, + {}, + ...(needsScrambleChecker ? [{}] : []), + {}, + {}, + {}, + {}, + {}, +]; + const noBorder = { border: [false, false, false, false] }; diff --git a/src/logic/translations.js b/src/logic/translations.js index b3e19fc..a54c2af 100644 --- a/src/logic/translations.js +++ b/src/logic/translations.js @@ -24,6 +24,10 @@ const texts = { scr: 'Scr', check: 'Check', result: 'Result', + del: 'Del', + solved: 'Solv', + declared: 'Decl', + time: 'Time', judge: 'Judge', comp: 'Comp', extra: 'Extra attempt', @@ -314,6 +318,10 @@ const texts = { scr: 'Miesz', check: 'Spr', result: 'Wynik', + del: 'Del', + solved: 'Ułożo', + declared: 'Dekla', + time: 'Czas', judge: 'Sędz', comp: 'Zaw', extra: 'Dodatkowe ułożenie', diff --git a/src/logic/wcif-extensions.js b/src/logic/wcif-extensions.js index a973b29..9855584 100644 --- a/src/logic/wcif-extensions.js +++ b/src/logic/wcif-extensions.js @@ -40,6 +40,7 @@ const defaultExtensionData = { printScrambleCheckerForTopRankedCompetitors: false, printScrambleCheckerForFinalRounds: false, printScrambleCheckerForBlankScorecards: false, + printDedicatedMultiBlindScorecards: false, }, };