Skip to content

Commit

Permalink
Consistently name activites in English in the EditSchedule component (#…
Browse files Browse the repository at this point in the history
…9401)

* Centrally localize activity name in EditSchedule

* Add helper method to switch locale at runtime

* Provide generic access key for FC activity attachment
  • Loading branch information
gregorbg authored Jun 10, 2024
1 parent e0574fd commit 25e759b
Show file tree
Hide file tree
Showing 6 changed files with 103 additions and 45 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,9 @@ import {
} from 'semantic-ui-react';
import cn from 'classnames';
import _ from 'lodash';
import {
parseActivityCode,
roundIdToString,
} from '../../../lib/utils/wcif';
import { shortLabelForActivityCode } from '../../../lib/utils/wcif';
import { formats } from '../../../lib/wca-data.js.erb';
import { activityToFcTitle, buildPartialActivityFromCode } from '../../../lib/utils/edit-schedule';

function ActivityPicker({
wcifEvents,
Expand Down Expand Up @@ -64,7 +62,6 @@ function PickerRow({
key={n}
wcifRoom={wcifRoom}
activityCode={`${wcifRound.id}-a${n + 1}`}
attemptNumber={n + 1}
/>
));
}
Expand All @@ -80,36 +77,26 @@ function PickerRow({
function ActivityLabel({
wcifRoom,
activityCode,
attemptNumber,
}) {
const usedActivityCodes = useMemo(
() => wcifRoom.activities.map((activity) => activity.activityCode),
[wcifRoom.activities],
);

const { roundNumber } = parseActivityCode(activityCode);

let tooltipText = roundIdToString(activityCode);
let text = `R${roundNumber}`;

if (attemptNumber) {
tooltipText += `, Attempt ${attemptNumber}`;
text += `A${attemptNumber}`;
}

const isEnabled = !usedActivityCodes.includes(activityCode);

const partialActivity = buildPartialActivityFromCode(activityCode);

return (
<Popup
content={tooltipText}
content={activityToFcTitle(partialActivity)}
trigger={(
<Label
className={isEnabled ? 'fc-draggable' : ''}
color={isEnabled ? 'blue' : 'grey'}
wcif-title={tooltipText}
wcif-ac={activityCode}
>
{text}
{shortLabelForActivityCode(activityCode)}
</Label>
)}
/>
Expand Down
44 changes: 30 additions & 14 deletions app/webpacker/components/EditSchedule/EditActivities/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,11 @@ import { useDispatch, useStore } from '../../../lib/providers/StoreProvider';
import { useConfirm } from '../../../lib/providers/ConfirmProvider';
import useInputState from '../../../lib/hooks/useInputState';
import ActivityPicker from './ActivityPicker';
import { getMatchingActivities, roomWcifFromId, venueWcifFromRoomId } from '../../../lib/utils/wcif';
import {
getMatchingActivities,
roomWcifFromId,
venueWcifFromRoomId,
} from '../../../lib/utils/wcif';
import { getTextColor } from '../../../lib/utils/calendar';
import useCheckboxState from '../../../lib/hooks/useCheckboxState';

Expand All @@ -40,7 +44,9 @@ import {

import { friendlyTimezoneName } from '../../../lib/wca-data.js.erb';
import {
defaultDurationFromActivityCode,
activityToFcTitle,
buildPartialActivityFromCode,
defaultDurationFromActivityCode, FC_ACTIVITY_ATTACHMENT,
fcEventToActivityAndDates,
luxonToWcifIso,
} from '../../../lib/utils/edit-schedule';
Expand Down Expand Up @@ -101,15 +107,14 @@ function EditActivities({
const matchCount = getMatchingActivities(wcifSchedule, activity).length - 1;
const matchesText = ` (${matchCount} matching activit${matchCount === 1 ? 'y' : 'ies'})`;

const fcTitle = activityToFcTitle(activity) + (shouldUpdateMatches && matchCount > 0 ? matchesText : '');

return {
title: activity.name + (shouldUpdateMatches && matchCount > 0 ? matchesText : ''),
title: fcTitle,
start: activity.startTime,
end: activity.endTime,
extendedProps: {
activityId: activity.id,
activityCode: activity.activityCode,
activityName: activity.name,
childActivities: activity.childActivities,
[FC_ACTIVITY_ATTACHMENT]: activity,
matchCount,
},
};
Expand All @@ -126,15 +131,15 @@ function EditActivities({
itemSelector: '.fc-draggable',
eventData: (eventEl) => {
const activityCode = eventEl.getAttribute('wcif-ac');
const activityName = eventEl.getAttribute('wcif-title');

const partialActivity = buildPartialActivityFromCode(activityCode);
const defaultDuration = defaultDurationFromActivityCode(activityCode);

return {
title: activityName,
title: activityToFcTitle(partialActivity),
duration: `00:${defaultDuration.toString().padStart(2, '0')}:00`,
extendedProps: {
activityCode,
activityName,
[FC_ACTIVITY_ATTACHMENT]: partialActivity,
},
};
},
Expand All @@ -146,6 +151,9 @@ function EditActivities({
const removeIfOverDropzone = ({ event: fcEvent, jsEvent }) => {
if (!dropToDeleteRef.current) return;

// Don't bother trying to delete an activity that hasn't even been added yet
if (!fcEvent.extendedProps[FC_ACTIVITY_ATTACHMENT]?.id) return;

const elem = dropToDeleteRef.current;
const rect = elem.getBoundingClientRect();

Expand All @@ -160,8 +168,16 @@ function EditActivities({
&& jsEvent.pageY >= top
&& jsEvent.pageY <= bottom
) {
const { activityId, activityName, matchCount } = fcEvent.extendedProps;
const {
[FC_ACTIVITY_ATTACHMENT]: {
id: activityId,
name: activityName,
},
matchCount,
} = fcEvent.extendedProps;

const matchText = `all ${matchCount + 1} copies of `;

confirm({
content: `Are you sure you want to delete ${shouldUpdateMatches && matchCount > 1 ? matchText : ''}the event ${activityName}? THIS ACTION CANNOT BE UNDONE!`,
}).then(() => {
Expand All @@ -181,7 +197,7 @@ function EditActivities({
delta,
view: { calendar },
}) => {
const { activityId } = fcEvent.extendedProps;
const { [FC_ACTIVITY_ATTACHMENT]: { id: activityId } } = fcEvent.extendedProps;

const duration = toLuxonDuration(delta, calendar);
const deltaIso = duration.toISO();
Expand All @@ -195,7 +211,7 @@ function EditActivities({
endDelta,
view: { calendar },
}) => {
const { activityId } = fcEvent.extendedProps;
const { [FC_ACTIVITY_ATTACHMENT]: { id: activityId } } = fcEvent.extendedProps;

const startScaleDuration = toLuxonDuration(startDelta, calendar);
const startScaleIso = startScaleDuration.toISO();
Expand Down
13 changes: 12 additions & 1 deletion app/webpacker/lib/i18n.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { registerLocale, setDefaultLocale } from 'react-datepicker';

const i18nFileContext = require.context('rails_translations');

const DEFAULT_LOCALE = 'en';
export const DEFAULT_LOCALE = 'en';

/**
* Use when I18n.t should return an array.
Expand Down Expand Up @@ -66,6 +66,17 @@ window.I18n.tArray = tArray;
*/
export default window.I18n;

export function withLocale(overrideLocale, fn) {
const actualLocale = window.I18n.locale;

try {
window.I18n.locale = overrideLocale;
return fn();
} finally {
window.I18n.locale = actualLocale;
}
}

function loadTranslations(i18n, locale) {
const translations = i18nFileContext(`./${locale}.json`);
i18n.store(translations);
Expand Down
36 changes: 25 additions & 11 deletions app/webpacker/lib/utils/edit-schedule.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import _ from 'lodash';
import { DateTime, Duration } from 'luxon';
import { toLuxonDateTime } from '@fullcalendar/luxon3';
import { parseActivityCode } from './wcif';
import { humanizeActivityCode, parseActivityCode } from './wcif';
import { DEFAULT_LOCALE, withLocale } from '../i18n';

export function toMicrodegrees(coord) {
const result = Math.trunc(parseFloat(coord) * 1e6);
Expand Down Expand Up @@ -134,27 +135,38 @@ export function changeTimezoneKeepingLocalTime(isoDateTime, oldTimezone, newTime
return luxonToWcifIso(newZoneSameLocalTime);
}

export const buildPartialActivityFromCode = (
activityCode,
childActivities = [],
extensions = [],
) => {
const humanizedCode = withLocale(DEFAULT_LOCALE, () => humanizeActivityCode(activityCode));

return {
name: humanizedCode,
activityCode,
childActivities,
extensions,
};
};

export const FC_ACTIVITY_ATTACHMENT = 'activityAttachment';

export function fcEventToActivityAndDates(fcEvent, calendar) {
const eventStartLuxon = toLuxonDateTime(fcEvent.start, calendar);
const eventEndLuxon = toLuxonDateTime(fcEvent.end, calendar);

const utcStartIso = luxonToWcifIso(eventStartLuxon);
const utcEndIso = luxonToWcifIso(eventEndLuxon);

const {
activityId,
activityCode,
activityName,
childActivities,
} = fcEvent.extendedProps;
const { [FC_ACTIVITY_ATTACHMENT]: attachedActivity } = fcEvent.extendedProps;
const partialActivity = buildPartialActivityFromCode(attachedActivity.activityCode);

const activity = {
id: activityId,
name: activityName,
activityCode,
...partialActivity,
...attachedActivity,
startTime: utcStartIso,
endTime: utcEndIso,
childActivities: childActivities || [],
};

return {
Expand All @@ -163,3 +175,5 @@ export function fcEventToActivityAndDates(fcEvent, calendar) {
endLuxon: eventEndLuxon,
};
}

export const activityToFcTitle = (activity) => activity.name;
28 changes: 28 additions & 0 deletions app/webpacker/lib/utils/wcif.js
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,34 @@ export const localizeActivityCode = (activityCode, wcifRound, wcifEvent) => {
return roundName;
};

export const humanizeActivityCode = (activityCode) => {
const { eventId, roundNumber, attempt } = parseActivityCode(activityCode);

const eventName = I18n.t(`events.${eventId}`);
const roundName = I18n.t('round.round_number', { round_number: roundNumber });

const tooltipText = `${eventName}, ${roundName}`;

if (attempt) {
const attemptName = I18n.t('attempts.attempt_name', { number: attempt });
return `${tooltipText}, ${attemptName}`;
}

return tooltipText;
};

export const shortLabelForActivityCode = (activityCode) => {
const { roundNumber, attempt } = parseActivityCode(activityCode);

const labelText = `R${roundNumber}`;

if (attempt) {
return `${labelText}A${attempt}`;
}

return labelText;
};

export function timeLimitToString(wcifRound, wcifEvents) {
const wcifTimeLimit = wcifRound.timeLimit;
const { eventId } = parseActivityCode(wcifRound.id);
Expand Down
2 changes: 2 additions & 0 deletions config/locales/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,8 @@ en:
round:
#context: e.g. event_name can be "3x3x3 Cube", and round_name can be "Cutoff First round"
name: "%{event_name} %{round_name}"
#context: round_number will be a numeral like 1,2,3 and NOT a written word like "First", "Second" or "Third".
round_number: "Round %{round_number}"
#context: Words used to describe the requirement to advance to the next round
advancement_condition:
short:
Expand Down

0 comments on commit 25e759b

Please sign in to comment.