Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/main' into glimmer-scoped-css-0.7.0
Browse files Browse the repository at this point in the history
  • Loading branch information
ef4 committed Dec 19, 2024
2 parents 90bea7c + b0bbab5 commit bf3a6c3
Show file tree
Hide file tree
Showing 122 changed files with 4,336 additions and 1,171 deletions.
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@
"deploy:boxel-motion:preview-staging": "cd packages/boxel-motion/addon && pnpm build && cd ../test-app && pnpm exec ember deploy s3-preview-staging --verbose",
"deploy:boxel-ui": "pnpm run build-common-deps && cd packages/boxel-ui/test-app && pnpm exec ember deploy",
"deploy:boxel-ui:preview-staging": "pnpm run build-common-deps && cd packages/boxel-ui/test-app && pnpm exec ember deploy s3-preview-staging --verbose",
"lint": "pnpm run --filter './packages/**' --if-present -r lint",
"lint:fix": "pnpm run --filter './packages/**' --if-present -r lint:fix"
"lint": "pnpm run --filter './packages/**' --filter '!./packages/boxel-motion/**' --if-present -r lint",
"lint:fix": "pnpm run --filter './packages/**' --filter '!./packages/boxel-motion/**' --if-present -r lint:fix"
},
"pnpm": {
"allowedDeprecatedVersions": {
Expand Down
43 changes: 28 additions & 15 deletions packages/base/card-api.gts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ import {
type CardDocument,
type CardResource,
type Actions,
type RealmInfo,
type CardResourceMeta,
CodeRef,
CommandContext,
} from '@cardstack/runtime-common';
Expand All @@ -61,7 +61,7 @@ interface CardorFieldTypeIconSignature {
Element: Element;
}

type CardorFieldTypeIcon = ComponentLike<CardorFieldTypeIconSignature>;
export type CardorFieldTypeIcon = ComponentLike<CardorFieldTypeIconSignature>;

export { primitive, isField, type BoxComponent };
export const serialize = Symbol.for('cardstack-serialize');
Expand All @@ -74,6 +74,7 @@ export const formatQuery = Symbol.for('cardstack-format-query');
export const relativeTo = Symbol.for('cardstack-relative-to');
export const realmInfo = Symbol.for('cardstack-realm-info');
export const realmURL = Symbol.for('cardstack-realm-url');
export const meta = Symbol.for('cardstack-meta');
// intentionally not exporting this so that the outside world
// cannot mark a card as being saved
const isSavedInstance = Symbol.for('cardstack-is-saved-instance');
Expand Down Expand Up @@ -1868,8 +1869,7 @@ export class MaybeBase64Field extends StringField {

export class CardDef extends BaseDef {
[isSavedInstance] = false;
[realmInfo]: RealmInfo | undefined = undefined;
[realmURL]: URL | undefined = undefined;
[meta]: CardResourceMeta | undefined = undefined;
@field id = contains(ReadOnlyField);
@field title = contains(StringField);
@field description = contains(StringField);
Expand Down Expand Up @@ -1909,6 +1909,15 @@ export class CardDef extends BaseDef {

static prefersWideFormat = false; // whether the card is full-width in the stack
static headerColor: string | null = null; // set string color value if the stack-item header has a background color

get [realmInfo]() {
return getCardMeta(this, 'realmInfo');
}

get [realmURL]() {
let realmURLString = getCardMeta(this, 'realmURL');
return realmURLString ? new URL(realmURLString) : undefined;
}
}

export type BaseDefConstructor = typeof BaseDef;
Expand Down Expand Up @@ -2324,7 +2333,11 @@ export function serializeCard(
if (!modelRelativeTo) {
return url.href;
}
return maybeRelativeURL(url, modelRelativeTo, model[realmURL]);
const realmURLString = getCardMeta(model, 'realmURL');
const realmURL = realmURLString
? new URL(realmURLString)
: undefined;
return maybeRelativeURL(url, modelRelativeTo, realmURL);
},
}
: {}),
Expand Down Expand Up @@ -2431,11 +2444,8 @@ export async function updateFromSerialized<T extends BaseDefConstructor>(
}

if (isCardInstance(instance)) {
if (!instance[realmInfo] && doc.data.meta.realmInfo) {
instance[realmInfo] = doc.data.meta.realmInfo;
}
if (!instance[realmURL] && doc.data.meta.realmURL) {
instance[realmURL] = new URL(doc.data.meta.realmURL);
if (!instance[meta] && doc.data.meta) {
instance[meta] = doc.data.meta;
}
}
return await _updateFromSerialized(instance, doc.data, doc, identityContext);
Expand Down Expand Up @@ -2480,10 +2490,7 @@ async function _createFromSerialized<T extends BaseDefConstructor>(
instance = new card({ id: resource.id }) as BaseInstanceType<T>;
instance[relativeTo] = _relativeTo;
if (isCardInstance(instance)) {
instance[realmInfo] = data?.meta?.realmInfo;
instance[realmURL] = data?.meta?.realmURL
? new URL(data.meta.realmURL)
: undefined;
instance[meta] = data?.meta;
}
}
identityContexts.set(instance, identityContext);
Expand Down Expand Up @@ -2963,7 +2970,6 @@ export function getFields(
return [];
}
}

return [[maybeFieldName, maybeField]];
});
fields = { ...fields, ...Object.fromEntries(currentFields) };
Expand Down Expand Up @@ -3132,3 +3138,10 @@ function myLoader(): Loader {
// @ts-ignore
return (import.meta as any).loader;
}

export function getCardMeta<K extends keyof CardResourceMeta>(
card: CardDef,
metaKey: K,
): CardResourceMeta[K] | undefined {
return card[meta]?.[metaKey] as CardResourceMeta[K] | undefined;
}
23 changes: 23 additions & 0 deletions packages/base/command.gts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
FieldDef,
StringField,
contains,
containsMany,
field,
linksTo,
linksToMany,
Expand Down Expand Up @@ -90,3 +91,25 @@ export class AddSkillsToRoomInput extends CardDef {
@field roomId = contains(StringField);
@field skills = linksToMany(SkillCard);
}

export class UpdateSkillActivationInput extends CardDef {
@field roomId = contains(StringField);
@field skillEventId = contains(StringField);
@field isActive = contains(BooleanField);
}

export class SendAiAssistantMessageInput extends CardDef {
@field roomId = contains(StringField);
@field prompt = contains(StringField);
@field clientGeneratedId = contains(StringField);
@field attachedCards = linksToMany(CardDef);

// This is a bit of a "fake" field in that it would not serialize properly.
// It works OK for the purposes of transient input to SendAiAssistantMessageCommand.
// The typescript type here is intended to be { command: Command<any, any, any>; autoExecute: boolean }[]
@field commands = containsMany(JsonField);
}

export class SendAiAssistantMessageResult extends CardDef {
@field eventId = contains(StringField);
}
2 changes: 2 additions & 0 deletions packages/boxel-ui/addon/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,9 @@
"@embroider/addon-shim": "^1.9.0",
"@floating-ui/dom": "^1.6.3",
"@glint/template": "1.3.0",
"awesome-phonenumber": "^7.2.0",
"classnames": "^2.3.2",
"countries-list": "^3.1.1",
"dayjs": "^1.11.7",
"ember-basic-dropdown": "^8.0.0",
"ember-css-url": "^1.0.0",
Expand Down
1 change: 1 addition & 0 deletions packages/boxel-ui/addon/raw-icons/star-filled.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions packages/boxel-ui/addon/raw-icons/star-half-fill.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions packages/boxel-ui/addon/raw-icons/star.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions packages/boxel-ui/addon/src/components.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import Modal from './components/modal/index.gts';
import BoxelMultiSelect, {
BoxelMultiSelectBasic,
} from './components/multi-select/index.gts';
import PhoneInput from './components/phone-input/index.gts';
import Pill from './components/pill/index.gts';
import ProgressBar from './components/progress-bar/index.gts';
import ProgressRadial from './components/progress-radial/index.gts';
Expand Down Expand Up @@ -92,6 +93,7 @@ export {
Menu,
Message,
Modal,
PhoneInput,
Pill,
ProgressBar,
ProgressRadial,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@ export const Select: TemplateOnlyComponent<SelectAccessorySignature> =
@onChange={{@onChange}}
@onBlur={{@onBlur}}
@matchTriggerWidth={{@matchTriggerWidth}}
@selectedItemComponent={{@selectedItemComponent}}
data-test-boxel-input-group-select-accessory-trigger
...attributes
as |item|
Expand Down
171 changes: 171 additions & 0 deletions packages/boxel-ui/addon/src/components/phone-input/index.gts
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
import { action } from '@ember/object';
import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
import {
getCountryCodeForRegionCode,
getExample,
getSupportedRegionCodes,
parsePhoneNumber,
} from 'awesome-phonenumber';
import { type TCountryCode, countries, getEmojiFlag } from 'countries-list';
import { debounce } from 'lodash';

import { type InputValidationState } from '../input/index.gts';
import BoxelInputGroup from '../input-group/index.gts';

interface Signature {
Args: {
countryCode: string;
onCountryCodeChange: (code: string) => void;
onInput: (value: string) => void;
value: string;
};
Blocks: {
default: [];
};
Element: HTMLElement;
}

interface CountryInfo {
callingCode?: string;
code: string;
example?: {
callingCode: string;
nationalNumber: string;
};
flag?: string;
name?: string;
}

const getCountryInfo = (countryCode: string): CountryInfo | undefined => {
let example = getExample(countryCode);
let callingCode = getCountryCodeForRegionCode(countryCode);

let c = countries[countryCode as TCountryCode];
if (c === undefined) {
//here some country code may not be found due to the discrepancy between countries-list and libphonenumber-js library
//Only scenario where this is true is the usage of "AC"
//Most countries consider "AC" Ascension Island as part of "SH" Saint Helena
return;
}
return {
code: countryCode,
callingCode: callingCode.toString(),
name: c ? c.name : undefined,
flag: getEmojiFlag(countryCode as TCountryCode),
example: example
? {
callingCode: callingCode.toString(),
nationalNumber: example.number?.international ?? '',
}
: undefined,
};
};

class PhoneInput extends Component<Signature> {
@tracked items: Array<CountryInfo> = [];
@tracked selectedItem: CountryInfo = getCountryInfo('US')!;
@tracked validationState: InputValidationState = 'initial';
@tracked input: string = this.args.value ?? '';

@action onSelectItem(item: CountryInfo): void {
this.selectedItem = item;
if (this.args.onCountryCodeChange) {
this.args.onCountryCodeChange(item.callingCode ?? '');
}
if (this.input.length > 0) {
const parsedPhoneNumber = parsePhoneNumber(this.input, {
regionCode: this.selectedItem.code,
});
this.validationState = parsedPhoneNumber.valid ? 'valid' : 'invalid';
}
}

constructor(owner: unknown, args: Signature['Args']) {
super(owner, args);
this.items = getSupportedRegionCodes()
.map((code) => {
return getCountryInfo(code);
})
.filter((c) => c !== undefined) as CountryInfo[];

if (this.args.countryCode) {
this.selectedItem = this.items.find(
(item) => item.callingCode === this.args.countryCode,
)!;
}
}

get placeholder(): string | undefined {
if (this.selectedItem) {
return this.selectedItem.example?.nationalNumber;
}
return undefined;
}

@action onInput(v: string): void {
this.debouncedInput(v);
}

private debouncedInput = debounce((input: string) => {
this.input = input;

if (input === '') {
this.validationState = 'initial';
return;
}

const parsedPhoneNumber = parsePhoneNumber(input, {
regionCode: this.selectedItem.code,
});
this.validationState = parsedPhoneNumber.valid ? 'valid' : 'invalid';
//save when the state is valid
if (this.validationState === 'valid') {
this.args.onInput(this.input);
}
}, 300);

<template>
<BoxelInputGroup
@placeholder={{this.placeholder}}
@state={{this.validationState}}
@onInput={{this.onInput}}
@value={{this.input}}
>
<:before as |Accessories|>
<Accessories.Select
@placeholder={{this.placeholder}}
@selected={{this.selectedItem}}
@onChange={{this.onSelectItem}}
@options={{this.items}}
@selectedItemComponent={{PhoneSelectedItem}}
@searchEnabled={{true}}
@searchField='name'
@matchTriggerWidth={{false}}
aria-label='Select an country calling code'
as |item|
>
<div>{{item.flag}} {{item.name}} +{{item.callingCode}}</div>
</Accessories.Select>
</:before>
</BoxelInputGroup>
</template>
}

export interface SelectedItemSignature {
Args: {
option: any;
};
Element: HTMLDivElement;
}

class PhoneSelectedItem extends Component<SelectedItemSignature> {
<template>
<div>
{{@option.flag}}
+{{@option.callingCode}}
</div>
</template>
}

export default PhoneInput;
Loading

0 comments on commit bf3a6c3

Please sign in to comment.