Introduce typed templates with Glint (#714)
This commit is contained in:
parent
146f531a37
commit
76586f165d
63 changed files with 859 additions and 216 deletions
|
@ -4,8 +4,6 @@ import loadInitializers from 'ember-load-initializers';
|
||||||
import config from 'croodle/config/environment';
|
import config from 'croodle/config/environment';
|
||||||
|
|
||||||
export default class App extends Application {
|
export default class App extends Application {
|
||||||
LOG_TRANSITIONS = true;
|
|
||||||
|
|
||||||
modulePrefix = config.modulePrefix;
|
modulePrefix = config.modulePrefix;
|
||||||
podModulePrefix = config.podModulePrefix;
|
podModulePrefix = config.podModulePrefix;
|
||||||
Resolver = Resolver;
|
Resolver = Resolver;
|
||||||
|
|
16
app/components/back-button.ts
Normal file
16
app/components/back-button.ts
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
import templateOnlyComponent from '@ember/component/template-only';
|
||||||
|
|
||||||
|
interface BackButtonSignature {
|
||||||
|
Args: { onClick?: () => void };
|
||||||
|
Element: HTMLButtonElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
const BackButton = templateOnlyComponent<BackButtonSignature>();
|
||||||
|
|
||||||
|
export default BackButton;
|
||||||
|
|
||||||
|
declare module '@glint/environment-ember-loose/registry' {
|
||||||
|
export default interface Registry {
|
||||||
|
BackButton: typeof BackButton;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
{{#let @form as |form|}}
|
{{#let @formElement as |FormElement|}}
|
||||||
<form.element
|
<FormElement
|
||||||
@label={{t "create.options.dates.label"}}
|
@label={{t "create.options.dates.label"}}
|
||||||
@property="options"
|
@property="options"
|
||||||
data-test-form-element-for="days"
|
data-test-form-element-for="days"
|
||||||
|
@ -17,10 +17,7 @@
|
||||||
<InlineDatepicker
|
<InlineDatepicker
|
||||||
@center={{this.calendarCenter}}
|
@center={{this.calendarCenter}}
|
||||||
@selectedDays={{this.selectedDays}}
|
@selectedDays={{this.selectedDays}}
|
||||||
@onCenterChange={{action
|
@onCenterChange={{fn this.handleCalenderCenterChange 0}}
|
||||||
(mut this.calendarCenter)
|
|
||||||
value="datetime"
|
|
||||||
}}
|
|
||||||
@onSelect={{this.handleSelectedDaysChange}}
|
@onSelect={{this.handleSelectedDaysChange}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -28,14 +25,11 @@
|
||||||
<InlineDatepicker
|
<InlineDatepicker
|
||||||
@center={{this.calendarCenterNext}}
|
@center={{this.calendarCenterNext}}
|
||||||
@selectedDays={{this.selectedDays}}
|
@selectedDays={{this.selectedDays}}
|
||||||
@onCenterChange={{action
|
@onCenterChange={{fn this.handleCalenderCenterChange -1}}
|
||||||
(mut this.calendarCenter)
|
|
||||||
value="datetime"
|
|
||||||
}}
|
|
||||||
@onSelect={{this.handleSelectedDaysChange}}
|
@onSelect={{this.handleSelectedDaysChange}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form.element>
|
</FormElement>
|
||||||
{{/let}}
|
{{/let}}
|
|
@ -3,12 +3,13 @@ import { action } from '@ember/object';
|
||||||
import { isArray } from '@ember/array';
|
import { isArray } from '@ember/array';
|
||||||
import { DateTime } from 'luxon';
|
import { DateTime } from 'luxon';
|
||||||
import { tracked } from '@glimmer/tracking';
|
import { tracked } from '@glimmer/tracking';
|
||||||
import type { TrackedSet } from 'tracked-built-ins';
|
|
||||||
import type { FormDataOption } from './create-options';
|
import type { FormDataOption } from './create-options';
|
||||||
|
import type BsFormElementComponent from 'ember-bootstrap/components/bs-form/element';
|
||||||
|
|
||||||
export interface CreateOptionsDatesSignature {
|
export interface CreateOptionsDatesSignature {
|
||||||
Args: {
|
Args: {
|
||||||
options: TrackedSet<FormDataOption>;
|
formElement: BsFormElementComponent;
|
||||||
|
options: Array<FormDataOption>;
|
||||||
updateOptions: (options: string[]) => void;
|
updateOptions: (options: string[]) => void;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -20,7 +21,7 @@ export default class CreateOptionsDates extends Component<CreateOptionsDatesSign
|
||||||
: DateTime.local();
|
: DateTime.local();
|
||||||
|
|
||||||
get selectedDays(): DateTime[] {
|
get selectedDays(): DateTime[] {
|
||||||
return Array.from(this.args.options).map(
|
return this.args.options.map(
|
||||||
({ value }) => DateTime.fromISO(value) as DateTime,
|
({ value }) => DateTime.fromISO(value) as DateTime,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -45,4 +46,18 @@ export default class CreateOptionsDates extends Component<CreateOptionsDatesSign
|
||||||
newDatesAsLuxonDateTime.map((datetime) => datetime.toISODate() as string),
|
newDatesAsLuxonDateTime.map((datetime) => datetime.toISODate() as string),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
handleCalenderCenterChange(
|
||||||
|
offset: number,
|
||||||
|
{ datetime }: { datetime: DateTime },
|
||||||
|
) {
|
||||||
|
this.calendarCenter = datetime.plus({ months: offset });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module '@glint/environment-ember-loose/registry' {
|
||||||
|
export default interface Registry {
|
||||||
|
CreateOptionsDates: typeof CreateOptionsDates;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<div class="cr-form-wrapper box">
|
<div class="cr-form-wrapper box">
|
||||||
{{#if this.errorMessage}}
|
{{#if this.errorMessage}}
|
||||||
<BsAlert type="warning">
|
<BsAlert @type="warning">
|
||||||
{{t this.errorMessage}}
|
{{t this.errorMessage}}
|
||||||
</BsAlert>
|
</BsAlert>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
@ -15,33 +15,15 @@
|
||||||
>
|
>
|
||||||
<div class="days">
|
<div class="days">
|
||||||
{{#each-in this.formData.datetimes as |date timeOptions|}}
|
{{#each-in this.formData.datetimes as |date timeOptions|}}
|
||||||
|
{{!--
|
||||||
|
@glint-ignore
|
||||||
|
Types for value returned by `{{#each-in}}` are broken if used
|
||||||
|
with a `Map`. https://github.com/typed-ember/glint/issues/645
|
||||||
|
--}}
|
||||||
{{#each timeOptions as |timeOption indexInTimeOptions|}}
|
{{#each timeOptions as |timeOption indexInTimeOptions|}}
|
||||||
{{!
|
<div data-test-day={{date}}>
|
||||||
show summarized validation state for all times in a day
|
|
||||||
}}
|
|
||||||
|
|
||||||
<div
|
|
||||||
{{!
|
|
||||||
TODO: daysValidationState is not defined!
|
|
||||||
}}
|
|
||||||
class={{if
|
|
||||||
(get this.daysValidationState date)
|
|
||||||
(concat "label-has-" (get this.daysValidationState date))
|
|
||||||
"label-has-no-validation"
|
|
||||||
}}
|
|
||||||
data-test-day={{date}}
|
|
||||||
>
|
|
||||||
<form.element
|
<form.element
|
||||||
{{!
|
@label={{format-date timeOption.jsDate dateStyle="full"}}
|
||||||
TODO: Simplify to dateStyle="full" after upgrading to Ember Intl v6
|
|
||||||
}}
|
|
||||||
@label={{format-date
|
|
||||||
timeOption.jsDate
|
|
||||||
weekday="long"
|
|
||||||
day="numeric"
|
|
||||||
month="long"
|
|
||||||
year="numeric"
|
|
||||||
}}
|
|
||||||
{{!
|
{{!
|
||||||
show label only for the first time of this date
|
show label only for the first time of this date
|
||||||
}}
|
}}
|
||||||
|
|
|
@ -102,6 +102,19 @@ class FormData {
|
||||||
return this.datetimes.size > 1;
|
return this.datetimes.size > 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get validationStatePerDate() {
|
||||||
|
const validationState: Map<string, boolean> = new Map();
|
||||||
|
|
||||||
|
for (const [date, timeOptions] of this.datetimes.entries()) {
|
||||||
|
validationState.set(
|
||||||
|
date,
|
||||||
|
Array.from(timeOptions).every((time) => time.isValid),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return validationState;
|
||||||
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
addOption(date: string) {
|
addOption(date: string) {
|
||||||
this.datetimes
|
this.datetimes
|
||||||
|
@ -215,7 +228,7 @@ export default class CreateOptionsDatetime extends Component<CreateOptoinsDateti
|
||||||
|
|
||||||
// validate input field for being partially filled
|
// validate input field for being partially filled
|
||||||
@action
|
@action
|
||||||
validateInput(option: FormDataTimeOption, event: InputEvent) {
|
validateInput(option: FormDataTimeOption, event: Event) {
|
||||||
const element = event.target as HTMLInputElement;
|
const element = event.target as HTMLInputElement;
|
||||||
|
|
||||||
// update partially filled time validation error
|
// update partially filled time validation error
|
||||||
|
@ -224,7 +237,7 @@ export default class CreateOptionsDatetime extends Component<CreateOptoinsDateti
|
||||||
|
|
||||||
// remove partially filled validation error if user fixed it
|
// remove partially filled validation error if user fixed it
|
||||||
@action
|
@action
|
||||||
updateInputValidation(option: FormDataTimeOption, event: InputEvent) {
|
updateInputValidation(option: FormDataTimeOption, event: Event) {
|
||||||
const element = event.target as HTMLInputElement;
|
const element = event.target as HTMLInputElement;
|
||||||
|
|
||||||
if (element.checkValidity() && option.isPartiallyFilled) {
|
if (element.checkValidity() && option.isPartiallyFilled) {
|
||||||
|
@ -266,3 +279,9 @@ export default class CreateOptionsDatetime extends Component<CreateOptoinsDateti
|
||||||
this.router.on('routeWillChange', this.handleTransition);
|
this.router.on('routeWillChange', this.handleTransition);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
declare module '@glint/environment-ember-loose/registry' {
|
||||||
|
export default interface Registry {
|
||||||
|
CreateOptionsDatetime: typeof CreateOptionsDatetime;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{{#let @form as |form|}}
|
{{#let @formElement as |FormElement|}}
|
||||||
{{#each @options as |option index|}}
|
{{#each @options as |option index|}}
|
||||||
<form.element
|
<FormElement
|
||||||
{{! show label only on first item }}
|
{{! show label only on first item }}
|
||||||
@label={{unless index (t "create.options.options.label")}}
|
@label={{unless index (t "create.options.options.label")}}
|
||||||
@model={{option}}
|
@model={{option}}
|
||||||
|
@ -48,6 +48,6 @@
|
||||||
></span>
|
></span>
|
||||||
<span class="sr-only">{{t "create.options.button.add.label"}}</span>
|
<span class="sr-only">{{t "create.options.button.add.label"}}</span>
|
||||||
</BsButton>
|
</BsButton>
|
||||||
</form.element>
|
</FormElement>
|
||||||
{{/each}}
|
{{/each}}
|
||||||
{{/let}}
|
{{/let}}
|
24
app/components/create-options-text.ts
Normal file
24
app/components/create-options-text.ts
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
import templateOnlyComponent from '@ember/component/template-only';
|
||||||
|
import type { FormDataOption } from './create-options';
|
||||||
|
import type BsFormElementComponent from 'ember-bootstrap/components/bs-form/element';
|
||||||
|
|
||||||
|
interface CreateOptionsTextSignature {
|
||||||
|
Args: {
|
||||||
|
Named: {
|
||||||
|
addOption: (value: string, afterPosition: number) => void;
|
||||||
|
deleteOption: (option: FormDataOption) => void;
|
||||||
|
formElement: BsFormElementComponent;
|
||||||
|
options: FormDataOption[];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const CreateOptionsText = templateOnlyComponent<CreateOptionsTextSignature>();
|
||||||
|
|
||||||
|
export default CreateOptionsText;
|
||||||
|
|
||||||
|
declare module '@glint/environment-ember-loose/registry' {
|
||||||
|
export default interface Registry {
|
||||||
|
CreateOptionsText: typeof CreateOptionsText;
|
||||||
|
}
|
||||||
|
}
|
|
@ -12,13 +12,13 @@
|
||||||
@options={{this.formData.options}}
|
@options={{this.formData.options}}
|
||||||
@addOption={{this.formData.addOption}}
|
@addOption={{this.formData.addOption}}
|
||||||
@deleteOption={{this.formData.deleteOption}}
|
@deleteOption={{this.formData.deleteOption}}
|
||||||
@form={{form}}
|
@formElement={{form.element}}
|
||||||
/>
|
/>
|
||||||
{{else}}
|
{{else}}
|
||||||
<CreateOptionsDates
|
<CreateOptionsDates
|
||||||
@options={{this.formData.options}}
|
@options={{this.formData.options}}
|
||||||
@updateOptions={{this.formData.updateOptions}}
|
@updateOptions={{this.formData.updateOptions}}
|
||||||
@form={{form}}
|
@formElement={{form.element}}
|
||||||
/>
|
/>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
|
@ -27,7 +27,7 @@
|
||||||
<NextButton />
|
<NextButton />
|
||||||
</div>
|
</div>
|
||||||
<div class="col-6 col-md-4 order-1 text-right">
|
<div class="col-6 col-md-4 order-1 text-right">
|
||||||
<BackButton @onClick={{action "previousPage"}} />
|
<BackButton @onClick={{this.previousPage}} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</BsForm>
|
</BsForm>
|
||||||
|
|
|
@ -104,7 +104,7 @@ export interface CreateOptionsSignature {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class CreateOptionsComponent extends Component<CreateOptionsSignature> {
|
export default class CreateOptions extends Component<CreateOptionsSignature> {
|
||||||
@service declare router: RouterService;
|
@service declare router: RouterService;
|
||||||
|
|
||||||
formData = new FormData(
|
formData = new FormData(
|
||||||
|
@ -136,3 +136,9 @@ export default class CreateOptionsComponent extends Component<CreateOptionsSigna
|
||||||
this.router.on('routeWillChange', this.handleTransition);
|
this.router.on('routeWillChange', this.handleTransition);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
declare module '@glint/environment-ember-loose/registry' {
|
||||||
|
export default interface Registry {
|
||||||
|
CreateOptions: typeof CreateOptions;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="ember-power-calendar-nav-control"
|
class="ember-power-calendar-nav-control"
|
||||||
onclick={{action calendar.actions.moveCenter -1 "month"}}
|
{{on "click" (fn calendar.actions.moveCenter -1 "month")}}
|
||||||
>
|
>
|
||||||
«
|
«
|
||||||
</button>
|
</button>
|
||||||
|
@ -19,7 +19,7 @@
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="ember-power-calendar-nav-control"
|
class="ember-power-calendar-nav-control"
|
||||||
onclick={{action calendar.actions.moveCenter 1 "month"}}
|
{{on "click" (fn calendar.actions.moveCenter 1 "month")}}
|
||||||
>
|
>
|
||||||
»
|
»
|
||||||
</button>
|
</button>
|
||||||
|
|
23
app/components/inline-datepicker.ts
Normal file
23
app/components/inline-datepicker.ts
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
import templateOnlyComponent from '@ember/component/template-only';
|
||||||
|
import type { DateTime } from 'luxon';
|
||||||
|
|
||||||
|
interface InlineDatepickerSignature {
|
||||||
|
Args: {
|
||||||
|
Named: {
|
||||||
|
center: DateTime;
|
||||||
|
onCenterChange: (day: { datetime: DateTime }) => void;
|
||||||
|
onSelect: (days: { datetime: DateTime[] }) => void;
|
||||||
|
selectedDays: DateTime[];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const InlineDatepicker = templateOnlyComponent<InlineDatepickerSignature>();
|
||||||
|
|
||||||
|
export default InlineDatepicker;
|
||||||
|
|
||||||
|
declare module '@glint/environment-ember-loose/registry' {
|
||||||
|
export default interface Registry {
|
||||||
|
InlineDatepicker: typeof InlineDatepicker;
|
||||||
|
}
|
||||||
|
}
|
|
@ -30,3 +30,9 @@ export default class LanguageSelect extends Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
declare module '@glint/environment-ember-loose/registry' {
|
||||||
|
export default interface Registry {
|
||||||
|
LanguageSelect: typeof LanguageSelect;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1 +1,5 @@
|
||||||
<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>
|
<span
|
||||||
|
class="spinner-border spinner-border-sm"
|
||||||
|
role="status"
|
||||||
|
aria-hidden="true"
|
||||||
|
></span>
|
13
app/components/loading-spinner.ts
Normal file
13
app/components/loading-spinner.ts
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
import templateOnlyComponent from '@ember/component/template-only';
|
||||||
|
|
||||||
|
interface LoadingSpinnerSignature {}
|
||||||
|
|
||||||
|
const LoadingSpinner = templateOnlyComponent<LoadingSpinnerSignature>();
|
||||||
|
|
||||||
|
export default LoadingSpinner;
|
||||||
|
|
||||||
|
declare module '@glint/environment-ember-loose/registry' {
|
||||||
|
export default interface Registry {
|
||||||
|
LoadingSpinner: typeof LoadingSpinner;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,7 +1,7 @@
|
||||||
<BsButton
|
<BsButton
|
||||||
@buttonType="submit"
|
|
||||||
@type="primary"
|
@type="primary"
|
||||||
class="cr-steps-bottom-nav__button cr-steps-bottom-nav__next-button next"
|
class="cr-steps-bottom-nav__button cr-steps-bottom-nav__next-button next"
|
||||||
|
type="submit"
|
||||||
...attributes
|
...attributes
|
||||||
>
|
>
|
||||||
<span class="cr-steps-bottom-nav__label">
|
<span class="cr-steps-bottom-nav__label">
|
||||||
|
|
20
app/components/next-button.ts
Normal file
20
app/components/next-button.ts
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
import templateOnlyComponent from '@ember/component/template-only';
|
||||||
|
|
||||||
|
interface NextButtonSignature {
|
||||||
|
Args: {
|
||||||
|
Named: {
|
||||||
|
isPending?: boolean;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
Element: HTMLButtonElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
const NextButton = templateOnlyComponent<NextButtonSignature>();
|
||||||
|
|
||||||
|
export default NextButton;
|
||||||
|
|
||||||
|
declare module '@glint/environment-ember-loose/registry' {
|
||||||
|
export default interface Registry {
|
||||||
|
NextButton: typeof NextButton;
|
||||||
|
}
|
||||||
|
}
|
|
@ -7,6 +7,12 @@
|
||||||
{{! column for name }}
|
{{! column for name }}
|
||||||
</th>
|
</th>
|
||||||
{{#each-in this.optionsPerDay as |jsDate count|}}
|
{{#each-in this.optionsPerDay as |jsDate count|}}
|
||||||
|
{{!
|
||||||
|
@glint-ignore
|
||||||
|
We can be sure that count is a number because it is destructed from a
|
||||||
|
Map, which values are only numbers. But somehow Glint / TypeScript
|
||||||
|
is not sure about it.
|
||||||
|
}}
|
||||||
<th colspan={{count}}>
|
<th colspan={{count}}>
|
||||||
{{!
|
{{!
|
||||||
TODO: Simplify to dateStyle="full" after upgrading to Ember Intl v6
|
TODO: Simplify to dateStyle="full" after upgrading to Ember Intl v6
|
||||||
|
@ -37,21 +43,23 @@
|
||||||
{{#if (and @poll.isFindADate @poll.hasTimes)}}
|
{{#if (and @poll.isFindADate @poll.hasTimes)}}
|
||||||
{{#if option.hasTime}}
|
{{#if option.hasTime}}
|
||||||
{{!
|
{{!
|
||||||
TODO: Simplify to timeStyle="short" after upgrading to Ember Intl v6
|
@glint-ignore
|
||||||
|
Narrowring is not working here correctly. Due to the only executing if
|
||||||
|
`option.hasTime` is `true`, we know that `option.jsDate` cannot be `null`.
|
||||||
|
But TypeScript does not support narrowing through a chain of getters
|
||||||
|
currently.
|
||||||
}}
|
}}
|
||||||
{{format-date option.jsDate hour="numeric" minute="numeric"}}
|
{{format-date option.jsDate timeStyle="short"}}
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{else if @poll.isFindADate}}
|
{{else if @poll.isFindADate}}
|
||||||
{{!
|
{{!
|
||||||
TODO: Simplify to dateStyle="full" after upgrading to Ember Intl v6
|
@glint-ignore
|
||||||
}}
|
Narrowring is not working here correctly. Due to the only executing if
|
||||||
{{format-date
|
`option.hasTime` is `true`, we know that `option.jsDate` cannot be `null`.
|
||||||
option.jsDate
|
But TypeScript does not support narrowing through a chain of getters
|
||||||
weekday="long"
|
currently.
|
||||||
day="numeric"
|
|
||||||
month="long"
|
|
||||||
year="numeric"
|
|
||||||
}}
|
}}
|
||||||
|
{{format-date option.jsDate dateStyle="full"}}
|
||||||
{{else}}
|
{{else}}
|
||||||
{{option.title}}
|
{{option.title}}
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
@ -67,7 +75,7 @@
|
||||||
{{user.name}}
|
{{user.name}}
|
||||||
</td>
|
</td>
|
||||||
{{#each @poll.options as |option index|}}
|
{{#each @poll.options as |option index|}}
|
||||||
{{#let (object-at index user.selections) as |selection|}}
|
{{#let (get user.selections index) as |selection|}}
|
||||||
<td
|
<td
|
||||||
class={{selection.type}}
|
class={{selection.type}}
|
||||||
data-test-is-selection-cell
|
data-test-is-selection-cell
|
||||||
|
|
|
@ -12,11 +12,19 @@ export default class PollEvaluationParticipantsTable extends Component<PollEvalu
|
||||||
get optionsPerDay() {
|
get optionsPerDay() {
|
||||||
const { poll } = this.args;
|
const { poll } = this.args;
|
||||||
|
|
||||||
const optionsPerDay = new Map();
|
const optionsPerDay: Map<string, number> = new Map();
|
||||||
for (const option of poll.options) {
|
for (const option of poll.options) {
|
||||||
|
if (!option.day) {
|
||||||
|
throw new Error(
|
||||||
|
`Excepts all options to have a valid ISO8601 date string when using optionsPerDay getter`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
optionsPerDay.set(
|
optionsPerDay.set(
|
||||||
option.day,
|
option.day,
|
||||||
optionsPerDay.has(option.day) ? optionsPerDay.get(option.day) + 1 : 0,
|
optionsPerDay.has(option.day)
|
||||||
|
? (optionsPerDay.get(option.day) as number) + 1
|
||||||
|
: 0,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -35,3 +43,9 @@ export default class PollEvaluationParticipantsTable extends Component<PollEvalu
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
declare module '@glint/environment-ember-loose/registry' {
|
||||||
|
export default interface Registry {
|
||||||
|
PollEvaluationParticipantsTable: typeof PollEvaluationParticipantsTable;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -2,24 +2,20 @@
|
||||||
There must not be a line break between option text and "</strong>." cause otherwise
|
There must not be a line break between option text and "</strong>." cause otherwise
|
||||||
we will see a space between option string and following dot.
|
we will see a space between option string and following dot.
|
||||||
}}
|
}}
|
||||||
{{#if @isFindADate}}
|
{{!
|
||||||
{{! Need to disable block indentation rule cause there shouldn't be a space between date and dot }}
|
Checking `@evaluationBestOption.option.jsDate` is the same as checking `@isFindADate`.
|
||||||
{{! template-lint-disable block-indentation }}
|
If poll type is `FindADate` we can be sure that every option is a valid ISO861
|
||||||
|
string. Therefore `Option.jsDate` must be `true` by design. But Glint / TypeScript
|
||||||
|
does not understand that. Therefore we need to use the less readable form.
|
||||||
|
}}
|
||||||
|
{{#if @evaluationBestOption.option.jsDate}}
|
||||||
<strong data-test-best-option={{@evaluationBestOption.option.title}}>
|
<strong data-test-best-option={{@evaluationBestOption.option.title}}>
|
||||||
{{!
|
|
||||||
TODO: Simplify to dateStyle="full" and timeStyle="short" after upgrading to Ember Intl v6
|
|
||||||
}}
|
|
||||||
{{format-date
|
{{format-date
|
||||||
@evaluationBestOption.option.jsDate
|
@evaluationBestOption.option.jsDate
|
||||||
weekday="long"
|
dateStyle="full"
|
||||||
day="numeric"
|
timeStyle=(if @evaluationBestOption.option.hasTime "short" undefined)
|
||||||
month="long"
|
|
||||||
year="numeric"
|
|
||||||
hour=(if @evaluationBestOption.option.hasTime "numeric" undefined)
|
|
||||||
minute=(if @evaluationBestOption.option.hasTime "numeric" undefined)
|
|
||||||
timeZone=(if @timeZone @timeZone undefined)
|
timeZone=(if @timeZone @timeZone undefined)
|
||||||
}}</strong>.
|
}}</strong>.
|
||||||
{{! template-lint-enable block-indentation }}
|
|
||||||
{{else}}
|
{{else}}
|
||||||
<strong
|
<strong
|
||||||
data-test-best-option={{@evaluationBestOption.option.title}}
|
data-test-best-option={{@evaluationBestOption.option.title}}
|
||||||
|
|
24
app/components/poll-evaluation-summary-option.ts
Normal file
24
app/components/poll-evaluation-summary-option.ts
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
import templateOnlyComponent from '@ember/component/template-only';
|
||||||
|
import type { BestOption } from './poll-evaluation-summary';
|
||||||
|
|
||||||
|
interface PollEvaluationSummaryOptionSignature {
|
||||||
|
Args: {
|
||||||
|
Named: {
|
||||||
|
evaluationBestOption: BestOption;
|
||||||
|
isFindADate: boolean;
|
||||||
|
timeZone: string | null | undefined;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
Element: HTMLButtonElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
const PollEvaluationSummaryOption =
|
||||||
|
templateOnlyComponent<PollEvaluationSummaryOptionSignature>();
|
||||||
|
|
||||||
|
export default PollEvaluationSummaryOption;
|
||||||
|
|
||||||
|
declare module '@glint/environment-ember-loose/registry' {
|
||||||
|
export default interface Registry {
|
||||||
|
PollEvaluationSummaryOption: typeof PollEvaluationSummaryOption;
|
||||||
|
}
|
||||||
|
}
|
|
@ -20,7 +20,7 @@
|
||||||
}}
|
}}
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
{{#if (get this.bestOptions.length 1)}}
|
{{#if (gt this.bestOptions.length 1)}}
|
||||||
<ul>
|
<ul>
|
||||||
{{#each this.bestOptions as |evaluationBestOption|}}
|
{{#each this.bestOptions as |evaluationBestOption|}}
|
||||||
<li>
|
<li>
|
||||||
|
@ -34,6 +34,11 @@
|
||||||
</ul>
|
</ul>
|
||||||
{{else}}
|
{{else}}
|
||||||
<PollEvaluationSummaryOption
|
<PollEvaluationSummaryOption
|
||||||
|
{{!
|
||||||
|
@glint-ignore
|
||||||
|
We can be sure that `this.bestOptions` contains at least one item
|
||||||
|
as a poll must always have at least one option.
|
||||||
|
}}
|
||||||
@evaluationBestOption={{get this.bestOptions 0}}
|
@evaluationBestOption={{get this.bestOptions 0}}
|
||||||
@isFindADate={{@poll.isFindADate}}
|
@isFindADate={{@poll.isFindADate}}
|
||||||
@timeZone={{@timeZone}}
|
@timeZone={{@timeZone}}
|
||||||
|
@ -42,9 +47,16 @@
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p class="last-participation">
|
<p class="last-participation">
|
||||||
{{t
|
{{#if this.lastParticipationAt}}
|
||||||
"poll.evaluation.lastParticipation"
|
{{t
|
||||||
ago=(format-date-relative this.lastParticipationAt)
|
"poll.evaluation.lastParticipation"
|
||||||
}}
|
ago=(format-date-relative this.lastParticipationAt)
|
||||||
|
}}
|
||||||
|
{{else}}
|
||||||
|
{{!
|
||||||
|
No need for the else block as user cannot enter evaluation page if
|
||||||
|
no one participated in the poll yet.
|
||||||
|
}}
|
||||||
|
{{/if}}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
|
@ -9,49 +9,67 @@ import type Poll from 'croodle/models/poll';
|
||||||
export interface PollEvaluationSummarySignature {
|
export interface PollEvaluationSummarySignature {
|
||||||
Args: {
|
Args: {
|
||||||
poll: Poll;
|
poll: Poll;
|
||||||
|
timeZone: string | undefined;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface BestOption {
|
||||||
|
answers: Record<'yes' | 'no' | 'maybe', number>;
|
||||||
|
option: Option;
|
||||||
|
score: number;
|
||||||
|
}
|
||||||
|
|
||||||
export default class PollEvaluationSummary extends Component<PollEvaluationSummarySignature> {
|
export default class PollEvaluationSummary extends Component<PollEvaluationSummarySignature> {
|
||||||
@service declare intl: IntlService;
|
@service declare intl: IntlService;
|
||||||
|
|
||||||
get bestOptions() {
|
get bestOptions(): BestOption[] | null {
|
||||||
const { poll } = this.args;
|
const { poll } = this.args;
|
||||||
const { isFreeText, options, users } = poll;
|
const { isFreeText, options, users } = poll;
|
||||||
|
|
||||||
// can not evaluate answer type free text
|
// can not evaluate answer type free text
|
||||||
if (isFreeText) {
|
if (isFreeText) {
|
||||||
return undefined;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// can not evaluate a poll without users
|
// can not evaluate a poll without users
|
||||||
if (users.length < 1) {
|
if (users.length < 1) {
|
||||||
return undefined;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const answers = poll.answers.reduce(
|
const answers = poll.answers.reduce(
|
||||||
(answers: Record<string, number>, answer: Answer) => {
|
(answers, answer: Answer) => {
|
||||||
answers[answer.type] = 0;
|
answers[answer.type] = 0;
|
||||||
return answers;
|
return answers;
|
||||||
},
|
},
|
||||||
{},
|
{} as Record<'yes' | 'no' | 'maybe', number>,
|
||||||
);
|
);
|
||||||
const evaluation: {
|
const evaluation: BestOption[] = options.map((option: Option) => {
|
||||||
answers: Record<string, number>;
|
|
||||||
option: Option;
|
|
||||||
score: number;
|
|
||||||
}[] = options.map((option: Option) => {
|
|
||||||
return {
|
return {
|
||||||
answers: { ...answers },
|
answers: { ...answers },
|
||||||
option,
|
option,
|
||||||
score: 0,
|
score: 0,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
const bestOptions = [];
|
|
||||||
|
|
||||||
users.forEach((user: User) => {
|
users.forEach((user: User) => {
|
||||||
user.selections.forEach(({ type }, i) => {
|
user.selections.forEach(({ type }, i) => {
|
||||||
evaluation[i]!.answers[type]++;
|
if (!type) {
|
||||||
|
// type may be undefined if poll does not force an answer to all options
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const evaluationForOption = evaluation[i];
|
||||||
|
if (evaluationForOption === undefined) {
|
||||||
|
throw new Error(
|
||||||
|
'Mismatch between number of options in poll and selections for user',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (type !== 'yes' && type !== 'no' && type !== 'maybe') {
|
||||||
|
throw new Error(
|
||||||
|
`Encountered not supported type of user selection: ${type}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
evaluationForOption.answers[type]++;
|
||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'yes':
|
case 'yes':
|
||||||
|
@ -71,10 +89,11 @@ export default class PollEvaluationSummary extends Component<PollEvaluationSumma
|
||||||
|
|
||||||
evaluation.sort((a, b) => b.score - a.score);
|
evaluation.sort((a, b) => b.score - a.score);
|
||||||
|
|
||||||
|
const bestOptions = [];
|
||||||
const bestScore = evaluation[0]!.score;
|
const bestScore = evaluation[0]!.score;
|
||||||
for (let i = 0; i < evaluation.length; i++) {
|
for (const evaluationForOption of evaluation) {
|
||||||
if (bestScore === evaluation[i]!.score) {
|
if (evaluationForOption.score === bestScore) {
|
||||||
bestOptions.push(evaluation[i]);
|
bestOptions.push(evaluationForOption);
|
||||||
} else {
|
} else {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -97,3 +116,9 @@ export default class PollEvaluationSummary extends Component<PollEvaluationSumma
|
||||||
return lastParticipationAt;
|
return lastParticipationAt;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
declare module '@glint/environment-ember-loose/registry' {
|
||||||
|
export default interface Registry {
|
||||||
|
PollEvaluationSummary: typeof PollEvaluationSummary;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<BsButton
|
<BsButton
|
||||||
@buttonType="submit"
|
|
||||||
@type="primary"
|
@type="primary"
|
||||||
class="cr-steps-bottom-nav__button cr-steps-bottom-nav__next-button next"
|
class="cr-steps-bottom-nav__button cr-steps-bottom-nav__next-button next"
|
||||||
|
type="submit"
|
||||||
...attributes
|
...attributes
|
||||||
>
|
>
|
||||||
<span class="cr-steps-bottom-nav__label">
|
<span class="cr-steps-bottom-nav__label">
|
||||||
|
|
20
app/components/save-button.ts
Normal file
20
app/components/save-button.ts
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
import templateOnlyComponent from '@ember/component/template-only';
|
||||||
|
|
||||||
|
interface SaveButtonSignature {
|
||||||
|
Args: {
|
||||||
|
Named: {
|
||||||
|
isPending: boolean;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
Element: HTMLButtonElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
const SaveButton = templateOnlyComponent<SaveButtonSignature>();
|
||||||
|
|
||||||
|
export default SaveButton;
|
||||||
|
|
||||||
|
declare module '@glint/environment-ember-loose/registry' {
|
||||||
|
export default interface Registry {
|
||||||
|
SaveButton: typeof SaveButton;
|
||||||
|
}
|
||||||
|
}
|
|
@ -85,4 +85,9 @@ export default class PollController extends Controller {
|
||||||
this.shouldUseLocalTimezone = true;
|
this.shouldUseLocalTimezone = true;
|
||||||
this.timezoneChoosen = true;
|
this.timezoneChoosen = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
usePollTimezone() {
|
||||||
|
this.timezoneChoosen = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,19 +9,27 @@ export interface FormatDateRelativeHelperSignature {
|
||||||
Args: {
|
Args: {
|
||||||
Positional: Positional;
|
Positional: Positional;
|
||||||
};
|
};
|
||||||
|
Return: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class FormatDateRelativeHelper extends Helper {
|
export default class FormatDateRelative extends Helper<FormatDateRelativeHelperSignature> {
|
||||||
@service declare intl: IntlService;
|
@service declare intl: IntlService;
|
||||||
|
|
||||||
compute([date]: Positional) {
|
compute([dateOrIsoString]: Positional) {
|
||||||
if (date instanceof Date) {
|
const isoString =
|
||||||
date = date.toISOString();
|
dateOrIsoString instanceof Date
|
||||||
}
|
? dateOrIsoString.toISOString()
|
||||||
|
: dateOrIsoString;
|
||||||
|
|
||||||
return DateTime.fromISO(date).toRelative({
|
return DateTime.fromISO(isoString).toRelative({
|
||||||
locale: this.intl.primaryLocale,
|
locale: this.intl.primaryLocale,
|
||||||
padding: 1000,
|
padding: 1000,
|
||||||
});
|
})!;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module '@glint/environment-ember-loose/registry' {
|
||||||
|
export default interface Registry {
|
||||||
|
'format-date-relative': typeof FormatDateRelative;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +0,0 @@
|
||||||
import { helper } from '@ember/component/helper';
|
|
||||||
import { htmlSafe } from '@ember/template';
|
|
||||||
|
|
||||||
export default helper(function markAsSafeHtml([html]) {
|
|
||||||
return htmlSafe(html);
|
|
||||||
});
|
|
|
@ -9,8 +9,14 @@ export interface MarkAsSafeHtmlHelperSignature {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export default helper<MarkAsSafeHtmlHelperSignature>(function markAsSafeHtml([
|
const markAsSafeHtml = helper<MarkAsSafeHtmlHelperSignature>(([html]) => {
|
||||||
html,
|
|
||||||
]) {
|
|
||||||
return htmlSafe(html);
|
return htmlSafe(html);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export default markAsSafeHtml;
|
||||||
|
|
||||||
|
declare module '@glint/environment-ember-loose/registry' {
|
||||||
|
export default interface Registry {
|
||||||
|
'mark-as-safe-html': typeof markAsSafeHtml;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -32,47 +32,53 @@ function elementIsNotVisible(element: Element) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function scrollFirstInvalidElementIntoViewPort() {
|
const scrollFirstInvalidElementIntoViewPort = helper(() => {
|
||||||
// `schedule('afterRender', function() {})` would be more approperiate but there seems to be a
|
return () => {
|
||||||
// timing issue in Firefox causing the Browser not scrolling up far enough if doing so
|
// `schedule('afterRender', function() {})` would be more approperiate but there seems to be a
|
||||||
// delaying to next runloop therefore
|
// timing issue in Firefox causing the Browser not scrolling up far enough if doing so
|
||||||
next(function () {
|
// delaying to next runloop therefore
|
||||||
const invalidInput = document.querySelector(
|
next(function () {
|
||||||
'.form-control.is-invalid, .custom-control-input.is-invalid',
|
const invalidInput = document.querySelector(
|
||||||
) as HTMLInputElement;
|
'.form-control.is-invalid, .custom-control-input.is-invalid',
|
||||||
assert(
|
) as HTMLInputElement;
|
||||||
'Atleast one form control must be marked as invalid if form submission was rejected as invalid',
|
assert(
|
||||||
invalidInput,
|
'Atleast one form control must be marked as invalid if form submission was rejected as invalid',
|
||||||
);
|
invalidInput,
|
||||||
|
);
|
||||||
|
|
||||||
// focus first invalid control
|
// focus first invalid control
|
||||||
invalidInput.focus({ preventScroll: true });
|
invalidInput.focus({ preventScroll: true });
|
||||||
|
|
||||||
// scroll to label or legend of first invalid control if it's not visible yet
|
// scroll to label or legend of first invalid control if it's not visible yet
|
||||||
if (elementIsNotVisible(invalidInput)) {
|
if (elementIsNotVisible(invalidInput)) {
|
||||||
// Radio groups have a label and a legend. While the label is per input, the legend is for
|
// Radio groups have a label and a legend. While the label is per input, the legend is for
|
||||||
// the whole group. Croodle should bring the legend into view in that case.
|
// the whole group. Croodle should bring the legend into view in that case.
|
||||||
// Due to a bug in Ember Bootstrap it renders a `<label>` instead of a `<legend>`:
|
// Due to a bug in Ember Bootstrap it renders a `<label>` instead of a `<legend>`:
|
||||||
// https://github.com/kaliber5/ember-bootstrap/issues/931
|
// https://github.com/kaliber5/ember-bootstrap/issues/931
|
||||||
// As a work-a-round we look the correct label up by a custom convention for the `id` of the
|
// As a work-a-round we look the correct label up by a custom convention for the `id` of the
|
||||||
// inputs and the `for` of the input group `<label>` (which should be a `<legend>`).
|
// inputs and the `for` of the input group `<label>` (which should be a `<legend>`).
|
||||||
const scrollTarget =
|
const scrollTarget =
|
||||||
document.querySelector(
|
document.querySelector(
|
||||||
`label[for="${invalidInput.id.substr(
|
`label[for="${invalidInput.id.substr(
|
||||||
0,
|
0,
|
||||||
invalidInput.id.indexOf('_'),
|
invalidInput.id.indexOf('_'),
|
||||||
)}"`,
|
)}"`,
|
||||||
) ||
|
) ||
|
||||||
document.querySelector(`label[for="${invalidInput.id}"]`) ||
|
document.querySelector(`label[for="${invalidInput.id}"]`) ||
|
||||||
// For polls with type `MakeAPoll` the option inputs do not have a label at all. In that case
|
// For polls with type `MakeAPoll` the option inputs do not have a label at all. In that case
|
||||||
// we scroll to the input element itself
|
// we scroll to the input element itself
|
||||||
invalidInput;
|
invalidInput;
|
||||||
|
|
||||||
scrollTarget.scrollIntoView({ behavior: 'smooth' });
|
scrollTarget.scrollIntoView({ behavior: 'smooth' });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
export default helper(function () {
|
|
||||||
return scrollFirstInvalidElementIntoViewPort;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export default scrollFirstInvalidElementIntoViewPort;
|
||||||
|
|
||||||
|
declare module '@glint/environment-ember-loose/registry' {
|
||||||
|
export default interface Registry {
|
||||||
|
'scroll-first-invalid-element-into-view-port': typeof scrollFirstInvalidElementIntoViewPort;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -27,7 +27,7 @@ export default class Option {
|
||||||
}
|
}
|
||||||
|
|
||||||
get day() {
|
get day() {
|
||||||
if (!this.datetime) {
|
if (this.datetime === null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -35,7 +35,7 @@ export default class Option {
|
||||||
}
|
}
|
||||||
|
|
||||||
get jsDate() {
|
get jsDate() {
|
||||||
if (!this.datetime) {
|
if (this.datetime === null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
import Modifier from 'ember-modifier';
|
import Modifier from 'ember-modifier';
|
||||||
|
|
||||||
type Named = {
|
type Named = {
|
||||||
enabled: boolean;
|
enabled?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
interface AutofocusModifierSignature {
|
interface AutofocusModifierSignature {
|
||||||
Element: HTMLInputElement;
|
Element: HTMLInputElement | HTMLSelectElement;
|
||||||
Args: {
|
Args: {
|
||||||
Named: Named;
|
Named: Named;
|
||||||
};
|
};
|
||||||
|
@ -29,3 +29,9 @@ export default class AutofocusModifier extends Modifier<AutofocusModifierSignatu
|
||||||
element.focus();
|
element.focus();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
declare module '@glint/environment-ember-loose/registry' {
|
||||||
|
export default interface Registry {
|
||||||
|
autofocus: typeof AutofocusModifier;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,20 +1,20 @@
|
||||||
{{page-title 'Croodle'}}
|
{{page-title "Croodle"}}
|
||||||
|
|
||||||
<nav class='cr-navbar navbar navbar-dark'>
|
<nav class="cr-navbar navbar navbar-dark">
|
||||||
<h1 class='cr-logo'>
|
<h1 class="cr-logo">
|
||||||
<LinkTo @route='index' class='navbar-brand'>
|
<LinkTo @route="index" class="navbar-brand">
|
||||||
Croodle
|
Croodle
|
||||||
</LinkTo>
|
</LinkTo>
|
||||||
</h1>
|
</h1>
|
||||||
<div class='collapse' id='headerNavbar'>
|
<div class="collapse" id="headerNavbar">
|
||||||
<form class='form-inline my-2 my-lg-0'>
|
<form class="form-inline my-2 my-lg-0">
|
||||||
<LanguageSelect class='custom-select custom-select-sm' />
|
<LanguageSelect />
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<main class='container cr-main'>
|
<main class="container cr-main">
|
||||||
<div id='messages'>
|
<div id="messages">
|
||||||
{{#each this.flashMessages.queue as |flash|}}
|
{{#each this.flashMessages.queue as |flash|}}
|
||||||
<FlashMessage @flash={{flash}}>
|
<FlashMessage @flash={{flash}}>
|
||||||
{{t flash.message}}
|
{{t flash.message}}
|
||||||
|
|
|
@ -1,23 +1,6 @@
|
||||||
{{page-title (t "create.title")}}
|
{{page-title (t "create.title")}}
|
||||||
|
|
||||||
<BsButtonGroup @justified={{true}} class="cr-steps-top-nav form-steps">
|
<BsButtonGroup @justified={{true}} class="cr-steps-top-nav form-steps">
|
||||||
{{#each this.formSteps as |formStep|}}
|
|
||||||
{{#unless formStep.hidden}}
|
|
||||||
<BsButton
|
|
||||||
@onClick={{fn this.transitionTo formStep.route}}
|
|
||||||
@type={{if
|
|
||||||
(eq this.router.currentRouteName formStep.route)
|
|
||||||
"primary is-active"
|
|
||||||
"default"
|
|
||||||
}}
|
|
||||||
disabled={{formStep.disabled}}
|
|
||||||
class="cr-steps-top-nav__button"
|
|
||||||
data-test-form-step={{formStep.route}}
|
|
||||||
>
|
|
||||||
{{t formStep.label}}
|
|
||||||
</BsButton>
|
|
||||||
{{/unless}}
|
|
||||||
{{/each}}
|
|
||||||
<BsButton
|
<BsButton
|
||||||
@onClick={{fn this.transitionTo "create.index"}}
|
@onClick={{fn this.transitionTo "create.index"}}
|
||||||
@type={{if
|
@type={{if
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
<CreateOptions
|
<CreateOptions
|
||||||
@isFindADate={{eq @model.pollType "FindADate"}}
|
|
||||||
@isMakeAPoll={{eq @model.pollType "MakeAPoll"}}
|
@isMakeAPoll={{eq @model.pollType "MakeAPoll"}}
|
||||||
@options={{if
|
@options={{if
|
||||||
(eq @model.pollType "FindADate")
|
(eq @model.pollType "FindADate")
|
||||||
|
|
|
@ -5,4 +5,4 @@
|
||||||
<p>
|
<p>
|
||||||
{{t "error.generic.unexpected.description"}}
|
{{t "error.generic.unexpected.description"}}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
|
@ -1,6 +0,0 @@
|
||||||
{{!--
|
|
||||||
Add content you wish automatically added to the documents head
|
|
||||||
here. The 'model' available in this template can be populated by
|
|
||||||
setting values on the 'head-data' service.
|
|
||||||
--}}
|
|
||||||
<title>{{this.model.title}}</title>
|
|
|
@ -34,8 +34,14 @@
|
||||||
<div class="col-lg-5 offset-lg-1">
|
<div class="col-lg-5 offset-lg-1">
|
||||||
<h3>{{t "index.hoster.title"}}</h3>
|
<h3>{{t "index.hoster.title"}}</h3>
|
||||||
<p>
|
<p>
|
||||||
{{t "index.hoster.text" gitHubLink=(mark-as-safe-html "<a href=\"https://github.com/jelhan/croodle\">GitHub</a>") htmlSafe=true}}
|
{{t
|
||||||
|
"index.hoster.text"
|
||||||
|
gitHubLink=(mark-as-safe-html
|
||||||
|
'<a href="https://github.com/jelhan/croodle">GitHub</a>'
|
||||||
|
)
|
||||||
|
htmlSafe=true
|
||||||
|
}}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
|
@ -29,4 +29,4 @@
|
||||||
{{t "error.generic.unexpected.description"}}
|
{{t "error.generic.unexpected.description"}}
|
||||||
</p>
|
</p>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</div>
|
</div>
|
|
@ -60,6 +60,7 @@
|
||||||
@onError={{fn this.linkAction "selected"}}
|
@onError={{fn this.linkAction "selected"}}
|
||||||
@onSuccess={{fn this.linkAction "copied"}}
|
@onSuccess={{fn this.linkAction "copied"}}
|
||||||
class="btn btn-secondary cr-poll-link__copy-btn btn-sm"
|
class="btn btn-secondary cr-poll-link__copy-btn btn-sm"
|
||||||
|
data-test-button="copy-link"
|
||||||
>
|
>
|
||||||
{{t "poll.link.copy-label"}}
|
{{t "poll.link.copy-label"}}
|
||||||
<span
|
<span
|
||||||
|
@ -145,7 +146,7 @@
|
||||||
{{t "poll.modal.timezoneDiffers.button.useLocalTimezone"}}
|
{{t "poll.modal.timezoneDiffers.button.useLocalTimezone"}}
|
||||||
</BsButton>
|
</BsButton>
|
||||||
<BsButton
|
<BsButton
|
||||||
@onClick={{action (mut this.timezoneChoosen) true}}
|
@onClick={{this.usePollTimezone}}
|
||||||
data-test-button="use-poll-timezone"
|
data-test-button="use-poll-timezone"
|
||||||
>
|
>
|
||||||
{{t "poll.modal.timezoneDiffers.button.usePollTimezone"}}
|
{{t "poll.modal.timezoneDiffers.button.usePollTimezone"}}
|
||||||
|
|
|
@ -24,10 +24,7 @@
|
||||||
{{#each @model.poll.options as |option index|}}
|
{{#each @model.poll.options as |option index|}}
|
||||||
{{#let
|
{{#let
|
||||||
(if
|
(if
|
||||||
(eq
|
(eq option.day (get (get @model.poll.options (sub index 1)) "day"))
|
||||||
option.day
|
|
||||||
(get (object-at (sub index 1) @model.poll.options) "day")
|
|
||||||
)
|
|
||||||
false
|
false
|
||||||
true
|
true
|
||||||
)
|
)
|
||||||
|
@ -57,7 +54,7 @@
|
||||||
)
|
)
|
||||||
option.title
|
option.title
|
||||||
}}
|
}}
|
||||||
@model={{object-at index @model.formData.selections}}
|
@model={{get @model.formData.selections index}}
|
||||||
@property="value"
|
@property="value"
|
||||||
data-test-form-element={{concat "option-" option.title}}
|
data-test-form-element={{concat "option-" option.title}}
|
||||||
/>
|
/>
|
||||||
|
@ -84,7 +81,7 @@
|
||||||
)
|
)
|
||||||
option.title
|
option.title
|
||||||
}}
|
}}
|
||||||
@model={{object-at index @model.formData.selections}}
|
@model={{get @model.formData.selections index}}
|
||||||
@property="value"
|
@property="value"
|
||||||
@showValidationOn="change"
|
@showValidationOn="change"
|
||||||
@useIcons={{false}}
|
@useIcons={{false}}
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { assert } from '@ember/debug';
|
||||||
export type Answer = {
|
export type Answer = {
|
||||||
labelTranslation: string;
|
labelTranslation: string;
|
||||||
icon: string;
|
icon: string;
|
||||||
type: string;
|
type: 'yes' | 'no' | 'maybe';
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function (
|
export default function (
|
||||||
|
|
|
@ -34,7 +34,7 @@ module.exports = function (defaults) {
|
||||||
enableTypeScriptTransform: true,
|
enableTypeScriptTransform: true,
|
||||||
},
|
},
|
||||||
'ember-composable-helpers': {
|
'ember-composable-helpers': {
|
||||||
only: ['array', 'object-at', 'pick'],
|
only: ['array', 'pick'],
|
||||||
},
|
},
|
||||||
'ember-math-helpers': {
|
'ember-math-helpers': {
|
||||||
only: ['lte', 'sub'],
|
only: ['lte', 'sub'],
|
||||||
|
|
132
package-lock.json
generated
132
package-lock.json
generated
|
@ -15,8 +15,9 @@
|
||||||
"@ember/test-helpers": "^3.2.0",
|
"@ember/test-helpers": "^3.2.0",
|
||||||
"@glimmer/component": "^1.1.2",
|
"@glimmer/component": "^1.1.2",
|
||||||
"@glimmer/tracking": "^1.1.2",
|
"@glimmer/tracking": "^1.1.2",
|
||||||
"@glint/environment-ember-loose": "^1.1.0",
|
"@glint/core": "^1.2.1",
|
||||||
"@glint/template": "^1.1.0",
|
"@glint/environment-ember-loose": "^1.2.1",
|
||||||
|
"@glint/template": "^1.2.1",
|
||||||
"@release-it-plugins/lerna-changelog": "^6.0.0",
|
"@release-it-plugins/lerna-changelog": "^6.0.0",
|
||||||
"@tsconfig/ember": "^3.0.1",
|
"@tsconfig/ember": "^3.0.1",
|
||||||
"@types/luxon": "^3.3.3",
|
"@types/luxon": "^3.3.3",
|
||||||
|
@ -8913,6 +8914,84 @@
|
||||||
"@simple-dom/interface": "^1.4.0"
|
"@simple-dom/interface": "^1.4.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@glint/core": {
|
||||||
|
"version": "1.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@glint/core/-/core-1.2.1.tgz",
|
||||||
|
"integrity": "sha512-25Zn65aLSN1M7s0D950sTNElZYRqa6HFA0xcT03iI/vQd1F6c3luMAXbFrsTSHlktZx2dqJ38c2dUnZJQBQgMw==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@glimmer/syntax": "^0.84.2",
|
||||||
|
"escape-string-regexp": "^4.0.0",
|
||||||
|
"semver": "^7.5.2",
|
||||||
|
"silent-error": "^1.1.1",
|
||||||
|
"uuid": "^8.3.2",
|
||||||
|
"vscode-languageserver": "^8.0.1",
|
||||||
|
"vscode-languageserver-textdocument": "^1.0.5",
|
||||||
|
"vscode-uri": "^3.0.8",
|
||||||
|
"yargs": "^17.5.1"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"glint": "bin/glint.js",
|
||||||
|
"glint-language-server": "bin/glint-language-server.js"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"typescript": ">=4.8.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@glint/core/node_modules/escape-string-regexp": {
|
||||||
|
"version": "4.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
|
||||||
|
"integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
|
||||||
|
"dev": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@glint/core/node_modules/lru-cache": {
|
||||||
|
"version": "6.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
|
||||||
|
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"yallist": "^4.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@glint/core/node_modules/semver": {
|
||||||
|
"version": "7.5.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
|
||||||
|
"integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"lru-cache": "^6.0.0"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"semver": "bin/semver.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@glint/core/node_modules/uuid": {
|
||||||
|
"version": "8.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
|
||||||
|
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
|
||||||
|
"dev": true,
|
||||||
|
"bin": {
|
||||||
|
"uuid": "dist/bin/uuid"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@glint/core/node_modules/yallist": {
|
||||||
|
"version": "4.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
|
||||||
|
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"node_modules/@glint/environment-ember-loose": {
|
"node_modules/@glint/environment-ember-loose": {
|
||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/@glint/environment-ember-loose/-/environment-ember-loose-1.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/@glint/environment-ember-loose/-/environment-ember-loose-1.2.1.tgz",
|
||||||
|
@ -56210,6 +56289,55 @@
|
||||||
"node": ">= 0.8"
|
"node": ">= 0.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/vscode-jsonrpc": {
|
||||||
|
"version": "8.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-8.1.0.tgz",
|
||||||
|
"integrity": "sha512-6TDy/abTQk+zDGYazgbIPc+4JoXdwC8NHU9Pbn4UJP1fehUyZmM4RHp5IthX7A6L5KS30PRui+j+tbbMMMafdw==",
|
||||||
|
"dev": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/vscode-languageserver": {
|
||||||
|
"version": "8.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/vscode-languageserver/-/vscode-languageserver-8.1.0.tgz",
|
||||||
|
"integrity": "sha512-eUt8f1z2N2IEUDBsKaNapkz7jl5QpskN2Y0G01T/ItMxBxw1fJwvtySGB9QMecatne8jFIWJGWI61dWjyTLQsw==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"vscode-languageserver-protocol": "3.17.3"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"installServerIntoExtension": "bin/installServerIntoExtension"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/vscode-languageserver-protocol": {
|
||||||
|
"version": "3.17.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.3.tgz",
|
||||||
|
"integrity": "sha512-924/h0AqsMtA5yK22GgMtCYiMdCOtWTSGgUOkgEDX+wk2b0x4sAfLiO4NxBxqbiVtz7K7/1/RgVrVI0NClZwqA==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"vscode-jsonrpc": "8.1.0",
|
||||||
|
"vscode-languageserver-types": "3.17.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/vscode-languageserver-textdocument": {
|
||||||
|
"version": "1.0.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.11.tgz",
|
||||||
|
"integrity": "sha512-X+8T3GoiwTVlJbicx/sIAF+yuJAqz8VvwJyoMVhwEMoEKE/fkDmrqUgDMyBECcM2A2frVZIUj5HI/ErRXCfOeA==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"node_modules/vscode-languageserver-types": {
|
||||||
|
"version": "3.17.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.3.tgz",
|
||||||
|
"integrity": "sha512-SYU4z1dL0PyIMd4Vj8YOqFvHu7Hz/enbWtpfnVbJHU4Nd1YNYx8u0ennumc6h48GQNeOLxmwySmnADouT/AuZA==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"node_modules/vscode-uri": {
|
||||||
|
"version": "3.0.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.0.8.tgz",
|
||||||
|
"integrity": "sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"node_modules/walk-sync": {
|
"node_modules/walk-sync": {
|
||||||
"version": "0.3.4",
|
"version": "0.3.4",
|
||||||
"resolved": "https://registry.npmjs.org/walk-sync/-/walk-sync-0.3.4.tgz",
|
"resolved": "https://registry.npmjs.org/walk-sync/-/walk-sync-0.3.4.tgz",
|
||||||
|
|
|
@ -19,7 +19,7 @@
|
||||||
"lint:hbs:fix": "ember-template-lint . --fix",
|
"lint:hbs:fix": "ember-template-lint . --fix",
|
||||||
"lint:js": "eslint . --cache",
|
"lint:js": "eslint . --cache",
|
||||||
"lint:js:fix": "eslint . --fix",
|
"lint:js:fix": "eslint . --fix",
|
||||||
"lint:types": "tsc --noEmit",
|
"lint:types": "glint",
|
||||||
"release": "release-it",
|
"release": "release-it",
|
||||||
"start": "ember serve",
|
"start": "ember serve",
|
||||||
"test": "concurrently \"npm:lint\" \"npm:test:*\" --names \"lint,test:\"",
|
"test": "concurrently \"npm:lint\" \"npm:test:*\" --names \"lint,test:\"",
|
||||||
|
@ -34,8 +34,9 @@
|
||||||
"@ember/test-helpers": "^3.2.0",
|
"@ember/test-helpers": "^3.2.0",
|
||||||
"@glimmer/component": "^1.1.2",
|
"@glimmer/component": "^1.1.2",
|
||||||
"@glimmer/tracking": "^1.1.2",
|
"@glimmer/tracking": "^1.1.2",
|
||||||
"@glint/environment-ember-loose": "^1.1.0",
|
"@glint/core": "^1.2.1",
|
||||||
"@glint/template": "^1.1.0",
|
"@glint/environment-ember-loose": "^1.2.1",
|
||||||
|
"@glint/template": "^1.2.1",
|
||||||
"@release-it-plugins/lerna-changelog": "^6.0.0",
|
"@release-it-plugins/lerna-changelog": "^6.0.0",
|
||||||
"@tsconfig/ember": "^3.0.1",
|
"@tsconfig/ember": "^3.0.1",
|
||||||
"@types/luxon": "^3.3.3",
|
"@types/luxon": "^3.3.3",
|
||||||
|
|
|
@ -10,8 +10,6 @@ import { setupApplicationTest } from 'ember-qunit';
|
||||||
import { setupMirage } from 'ember-cli-mirage/test-support';
|
import { setupMirage } from 'ember-cli-mirage/test-support';
|
||||||
import pageParticipation from 'croodle/tests/pages/poll/participation';
|
import pageParticipation from 'croodle/tests/pages/poll/participation';
|
||||||
import { DateTime } from 'luxon';
|
import { DateTime } from 'luxon';
|
||||||
import { triggerCopySuccess } from 'ember-cli-clipboard/test-support';
|
|
||||||
|
|
||||||
module('Acceptance | view poll', function (hooks) {
|
module('Acceptance | view poll', function (hooks) {
|
||||||
hooks.beforeEach(function () {
|
hooks.beforeEach(function () {
|
||||||
window.localStorage.setItem('locale', 'en');
|
window.localStorage.setItem('locale', 'en');
|
||||||
|
@ -32,7 +30,7 @@ module('Acceptance | view poll', function (hooks) {
|
||||||
'share link is shown',
|
'share link is shown',
|
||||||
);
|
);
|
||||||
|
|
||||||
await triggerCopySuccess();
|
await click('.copy-btn');
|
||||||
/*
|
/*
|
||||||
* Can't test if link is actually copied to clipboard due to api
|
* Can't test if link is actually copied to clipboard due to api
|
||||||
* restrictions. Due to security it's not allowed to read from clipboard.
|
* restrictions. Due to security it's not allowed to read from clipboard.
|
||||||
|
|
|
@ -13,5 +13,8 @@
|
||||||
],
|
],
|
||||||
"*": ["types/*"]
|
"*": ["types/*"]
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"glint": {
|
||||||
|
"environment": "ember-loose"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
17
types/ember-bootstrap/components/bs-alert.d.ts
vendored
Normal file
17
types/ember-bootstrap/components/bs-alert.d.ts
vendored
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
import { ComponentLike } from '@glint/template';
|
||||||
|
|
||||||
|
declare module '@glint/environment-ember-loose/registry' {
|
||||||
|
export default interface Registry {
|
||||||
|
BsAlert: ComponentLike<{
|
||||||
|
Args: {
|
||||||
|
Named: {
|
||||||
|
type: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
Blocks: {
|
||||||
|
default: [];
|
||||||
|
};
|
||||||
|
Element: HTMLDivElement;
|
||||||
|
}>;
|
||||||
|
}
|
||||||
|
}
|
17
types/ember-bootstrap/components/bs-button-group.d.ts
vendored
Normal file
17
types/ember-bootstrap/components/bs-button-group.d.ts
vendored
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
import { ComponentLike } from '@glint/template';
|
||||||
|
|
||||||
|
declare module '@glint/environment-ember-loose/registry' {
|
||||||
|
export default interface Registry {
|
||||||
|
BsButtonGroup: ComponentLike<{
|
||||||
|
Args: {
|
||||||
|
Named: {
|
||||||
|
justified: boolean;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
Blocks: {
|
||||||
|
default: [];
|
||||||
|
};
|
||||||
|
Element: HTMLDivElement;
|
||||||
|
}>;
|
||||||
|
}
|
||||||
|
}
|
19
types/ember-bootstrap/components/bs-button.d.ts
vendored
Normal file
19
types/ember-bootstrap/components/bs-button.d.ts
vendored
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
import { ComponentLike } from '@glint/template';
|
||||||
|
|
||||||
|
declare module '@glint/environment-ember-loose/registry' {
|
||||||
|
export default interface Registry {
|
||||||
|
BsButton: ComponentLike<{
|
||||||
|
Args: {
|
||||||
|
Named: {
|
||||||
|
onClick?: () => void;
|
||||||
|
size?: 'sm' | 'md' | 'lg';
|
||||||
|
type?: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
Blocks: {
|
||||||
|
default: [];
|
||||||
|
};
|
||||||
|
Element: HTMLButtonElement;
|
||||||
|
}>;
|
||||||
|
}
|
||||||
|
}
|
30
types/ember-bootstrap/components/bs-form.d.ts
vendored
Normal file
30
types/ember-bootstrap/components/bs-form.d.ts
vendored
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
import { ComponentLike } from '@glint/template';
|
||||||
|
import type BsFormElementComponent from './bs-form/element';
|
||||||
|
|
||||||
|
type BsFormComponent = ComponentLike<{
|
||||||
|
Args: {
|
||||||
|
Named: {
|
||||||
|
formLayout: 'horizontal' | 'vertical';
|
||||||
|
model: unknown;
|
||||||
|
onInvalid?: () => void;
|
||||||
|
onSubmit: () => void;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
Blocks: {
|
||||||
|
default: [
|
||||||
|
{
|
||||||
|
element: BsFormElementComponent;
|
||||||
|
isSubmitting: boolean;
|
||||||
|
},
|
||||||
|
];
|
||||||
|
};
|
||||||
|
Element: HTMLDivElement;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
export default BsFormComponent;
|
||||||
|
|
||||||
|
declare module '@glint/environment-ember-loose/registry' {
|
||||||
|
export default interface Registry {
|
||||||
|
BsForm: BsFormComponent;
|
||||||
|
}
|
||||||
|
}
|
34
types/ember-bootstrap/components/bs-form/element.d.ts
vendored
Normal file
34
types/ember-bootstrap/components/bs-form/element.d.ts
vendored
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
import { ComponentLike } from '@glint/template';
|
||||||
|
|
||||||
|
type BsFormElementComponent = ComponentLike<{
|
||||||
|
Args: {
|
||||||
|
Named: {
|
||||||
|
controlType?: 'checkbox' | 'select' | 'text' | 'textarea' | 'time';
|
||||||
|
invisibleLabel?: boolean;
|
||||||
|
label?: string;
|
||||||
|
model?: unknown;
|
||||||
|
property?: string;
|
||||||
|
showValidationOn?: string | string[];
|
||||||
|
useIcons?: boolean;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
Blocks: {
|
||||||
|
default: [
|
||||||
|
{
|
||||||
|
control: ComponentLike<{
|
||||||
|
Args: {
|
||||||
|
Named: Record<string, unknown>;
|
||||||
|
};
|
||||||
|
Element: HTMLInputElement;
|
||||||
|
}>;
|
||||||
|
id: string;
|
||||||
|
setValue: (value: unknown) => void;
|
||||||
|
validation: 'success' | 'error' | 'warning' | null;
|
||||||
|
value: unknown;
|
||||||
|
},
|
||||||
|
];
|
||||||
|
};
|
||||||
|
Element: HTMLDivElement;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
export default BsFormElementComponent;
|
35
types/ember-bootstrap/components/bs-modal.d.ts
vendored
Normal file
35
types/ember-bootstrap/components/bs-modal.d.ts
vendored
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
import { ComponentLike } from '@glint/template';
|
||||||
|
|
||||||
|
declare module '@glint/environment-ember-loose/registry' {
|
||||||
|
export default interface Registry {
|
||||||
|
BsModal: ComponentLike<{
|
||||||
|
Args: {
|
||||||
|
Named: {
|
||||||
|
autoClose: boolean;
|
||||||
|
closeButton: boolean;
|
||||||
|
footer: boolean;
|
||||||
|
keyboard: boolean;
|
||||||
|
open: boolean;
|
||||||
|
title: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
Blocks: {
|
||||||
|
default: [
|
||||||
|
{
|
||||||
|
body: ComponentLike<{
|
||||||
|
Blocks: {
|
||||||
|
default: [];
|
||||||
|
};
|
||||||
|
}>;
|
||||||
|
footer: ComponentLike<{
|
||||||
|
Blocks: {
|
||||||
|
default: [];
|
||||||
|
};
|
||||||
|
}>;
|
||||||
|
},
|
||||||
|
];
|
||||||
|
};
|
||||||
|
Element: HTMLDivElement;
|
||||||
|
}>;
|
||||||
|
}
|
||||||
|
}
|
19
types/ember-cli-clipboard/components/copy-button.d.ts
vendored
Normal file
19
types/ember-cli-clipboard/components/copy-button.d.ts
vendored
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
import { ComponentLike } from '@glint/template';
|
||||||
|
|
||||||
|
declare module '@glint/environment-ember-loose/registry' {
|
||||||
|
export default interface Registry {
|
||||||
|
CopyButton: ComponentLike<{
|
||||||
|
Args: {
|
||||||
|
Named: {
|
||||||
|
onError: () => void;
|
||||||
|
onSuccess: () => void;
|
||||||
|
text: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
Blocks: {
|
||||||
|
default: [];
|
||||||
|
};
|
||||||
|
Element: HTMLButtonElement;
|
||||||
|
}>;
|
||||||
|
}
|
||||||
|
}
|
17
types/ember-cli-flash/components/flash-message.d.ts
vendored
Normal file
17
types/ember-cli-flash/components/flash-message.d.ts
vendored
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
import { ComponentLike } from '@glint/template';
|
||||||
|
import type FlashObject from 'ember-cli-flash/flash/object';
|
||||||
|
|
||||||
|
declare module '@glint/environment-ember-loose/registry' {
|
||||||
|
export default interface Registry {
|
||||||
|
FlashMessage: ComponentLike<{
|
||||||
|
Args: {
|
||||||
|
Named: {
|
||||||
|
flash: FlashObject;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
Blocks: {
|
||||||
|
default: [];
|
||||||
|
};
|
||||||
|
}>;
|
||||||
|
}
|
||||||
|
}
|
1
types/ember-cli-flash/flash/object.d.ts
vendored
1
types/ember-cli-flash/flash/object.d.ts
vendored
|
@ -8,6 +8,7 @@ declare module 'ember-cli-flash/flash/object' {
|
||||||
exitTimer: number;
|
exitTimer: number;
|
||||||
isExitable: boolean;
|
isExitable: boolean;
|
||||||
initializedTime: number;
|
initializedTime: number;
|
||||||
|
message: string;
|
||||||
destroyMessage(): void;
|
destroyMessage(): void;
|
||||||
exitMessage(): void;
|
exitMessage(): void;
|
||||||
preventExit(): void;
|
preventExit(): void;
|
||||||
|
|
12
types/ember-composable-helpers/helpers/pick.d.ts
vendored
Normal file
12
types/ember-composable-helpers/helpers/pick.d.ts
vendored
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
import { HelperLike } from '@glint/template';
|
||||||
|
|
||||||
|
declare module '@glint/environment-ember-loose/registry' {
|
||||||
|
export default interface Registry {
|
||||||
|
pick: HelperLike<{
|
||||||
|
Args: {
|
||||||
|
Positional: [path: string, action?: (_: unknown) => unknown];
|
||||||
|
};
|
||||||
|
Return: (_: unknown) => unknown;
|
||||||
|
}>;
|
||||||
|
}
|
||||||
|
}
|
18
types/ember-intl/helpers/format-date.d.ts
vendored
Normal file
18
types/ember-intl/helpers/format-date.d.ts
vendored
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
import { HelperLike } from '@glint/template';
|
||||||
|
|
||||||
|
// Ember Intl ships glint types. But as of today (October 29, 2023)
|
||||||
|
// they are buggy and cannot be used.
|
||||||
|
// Types provided by Ember Intl should be used instead as soon as
|
||||||
|
// type issues have been fixed in the addon itself.
|
||||||
|
|
||||||
|
declare module '@glint/environment-ember-loose/registry' {
|
||||||
|
export default interface Registry {
|
||||||
|
'format-date': HelperLike<{
|
||||||
|
Args: {
|
||||||
|
Positional: [Date | string];
|
||||||
|
Named: Record<string, unknown>;
|
||||||
|
};
|
||||||
|
Return: string;
|
||||||
|
}>;
|
||||||
|
}
|
||||||
|
}
|
18
types/ember-intl/helpers/t.d.ts
vendored
Normal file
18
types/ember-intl/helpers/t.d.ts
vendored
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
import { HelperLike } from '@glint/template';
|
||||||
|
|
||||||
|
// Ember Intl ships glint types. But as of today (October 29, 2023)
|
||||||
|
// they are buggy and cannot be used.
|
||||||
|
// Types provided by Ember Intl should be used instead as soon as
|
||||||
|
// type issues have been fixed in the addon itself.
|
||||||
|
|
||||||
|
declare module '@glint/environment-ember-loose/registry' {
|
||||||
|
export default interface Registry {
|
||||||
|
t: HelperLike<{
|
||||||
|
Args: {
|
||||||
|
Positional: [string];
|
||||||
|
Named: Record<string, unknown>;
|
||||||
|
};
|
||||||
|
Return: string;
|
||||||
|
}>;
|
||||||
|
}
|
||||||
|
}
|
15
types/ember-page-title/helpers/page-title.d.ts
vendored
Normal file
15
types/ember-page-title/helpers/page-title.d.ts
vendored
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
import { HelperLike } from '@glint/template';
|
||||||
|
|
||||||
|
// Glint support in ember-page-title itself is tracked here:
|
||||||
|
// https://github.com/ember-cli/ember-page-title/issues/239
|
||||||
|
|
||||||
|
declare module '@glint/environment-ember-loose/registry' {
|
||||||
|
export default interface Registry {
|
||||||
|
'page-title': HelperLike<{
|
||||||
|
Args: {
|
||||||
|
Positional: [string];
|
||||||
|
};
|
||||||
|
Return: void;
|
||||||
|
}>;
|
||||||
|
}
|
||||||
|
}
|
29
types/ember-power-calendar/components/power-calendar-mutliple.d.ts
vendored
Normal file
29
types/ember-power-calendar/components/power-calendar-mutliple.d.ts
vendored
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
import { ComponentLike } from '@glint/template';
|
||||||
|
import type { DateTime } from 'luxon';
|
||||||
|
|
||||||
|
declare module '@glint/environment-ember-loose/registry' {
|
||||||
|
export default interface Registry {
|
||||||
|
PowerCalendarMultiple: ComponentLike<{
|
||||||
|
Args: {
|
||||||
|
Named: {
|
||||||
|
center: DateTime;
|
||||||
|
onCenterChange: (day: { datetime: DateTime }) => void;
|
||||||
|
onSelect: (days: { datetime: DateTime[] }) => void;
|
||||||
|
selected: DateTime[];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
Blocks: {
|
||||||
|
default: [
|
||||||
|
{
|
||||||
|
actions: {
|
||||||
|
moveCenter: (step: number, unit: 'month') => void;
|
||||||
|
};
|
||||||
|
center: Date;
|
||||||
|
Days: ComponentLike;
|
||||||
|
},
|
||||||
|
];
|
||||||
|
};
|
||||||
|
Element: HTMLDivElement;
|
||||||
|
}>;
|
||||||
|
}
|
||||||
|
}
|
8
types/global.d.ts
vendored
8
types/global.d.ts
vendored
|
@ -1 +1,9 @@
|
||||||
import '@glint/environment-ember-loose';
|
import '@glint/environment-ember-loose';
|
||||||
|
import type EmberMathRegistry from 'ember-math-helpers/template-registry';
|
||||||
|
import type EmberTruthRegistry from 'ember-truth-helpers/template-registry';
|
||||||
|
|
||||||
|
declare module '@glint/environment-ember-loose/registry' {
|
||||||
|
export default interface Registry
|
||||||
|
extends EmberMathRegistry,
|
||||||
|
EmberTruthRegistry {}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue