Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions addon/components/country-select.hbs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<div class="fleetbase-power-select {{@wrapperClass}}">
<div class="fleetbase-power-select {{@wrapperClass}}" {{did-update this.handleChange @value}}>
<PowerSelect
@renderInPlace={{true}}
@renderInPlace={{this.renderInPlace}}
@searchEnabled={{true}}
@selected={{this.selected}}
@searchField="name"
Expand All @@ -17,5 +17,4 @@
<span class="mr-1">{{country.emoji}}</span>
<span>{{country.name}}</span>
</PowerSelect>
<Input @type="hidden" class="hidden" @value={{@value}} aria-label="Country Value" data-county={{@value}} {{did-insert this.listenForInputChanges}} />
</div>
26 changes: 12 additions & 14 deletions addon/components/country-select.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { tracked } from '@glimmer/tracking';
import { action } from '@ember/object';
import { guidFor } from '@ember/object/internals';
import { task } from 'ember-concurrency';
import { later } from '@ember/runloop';

export default class CountrySelectComponent extends Component {
@service fetch;
Expand All @@ -14,6 +13,10 @@ export default class CountrySelectComponent extends Component {
@tracked value;
@tracked id = guidFor(this);

get renderInPlace() {
return this.args.renderInPlace ?? true;
}

constructor(owner, { value = null, disabled = false }) {
super(...arguments);
this.disabled = disabled;
Expand All @@ -23,10 +26,12 @@ export default class CountrySelectComponent extends Component {

@task *fetchCountries(value = null) {
try {
this.countries = yield this.fetch.get('lookup/countries', { columns: ['name', 'cca2', 'flag', 'emoji'] });
if (value) {
this.selected = this.findCountry(value);
}
this.countries = yield this.fetch.get(
'lookup/countries',
{ columns: ['name', 'cca2', 'flag', 'emoji'] },
{ fromCache: true, expirationInterval: 1, expirationIntervalUnit: 'week' }
);
this.selected = this.findCountry(value);
} catch (error) {
this.countries = [];
}
Expand All @@ -40,15 +45,8 @@ export default class CountrySelectComponent extends Component {
}
}

@action listenForInputChanges(element) {
later(() => {
const { value } = element;

if (this.value !== value) {
this.value = value;
this.changed(value);
}
}, 100);
@action handleChange(el, [value]) {
this.selected = this.findCountry(value);
}

@action selectCountry(country) {
Expand Down
1 change: 1 addition & 0 deletions addon/components/layout/resource/panel.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
@fullHeight={{true}}
@isResizable={{this.isResizable}}
@width={{this.width}}
...attributes
>
{{#if @headerComponent}}
{{component
Expand Down
4 changes: 2 additions & 2 deletions addon/components/locale-selector-tray.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@
</dd.Trigger>
<dd.Content class="{{@contentClass}} locale-selector-tray-content {{if (media 'isMobile') 'is-mobile'}}">
<div class="next-dd-menu {{@dropdownMenuClass}} {{if dd.isOpen 'is-open'}}">
{{#if this.loadAvailableCountries.isRunning}}
{{#if this.language.loadAvailableCountries.isRunning}}
<Spinner />
{{else}}
{{#each-in this.availableLocales as |key country|}}
{{#each-in this.language.availableLocales as |key country|}}
<div class="px-1">
<a href="javascript:;" class="next-dd-item" {{on "click" (fn this.changeLocale key)}}>
<div class="flex flex-row items-center justify-between w-full">
Expand Down
85 changes: 1 addition & 84 deletions addon/components/locale-selector-tray.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,34 +2,15 @@ import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
import { inject as service } from '@ember/service';
import { action } from '@ember/object';
import { debug } from '@ember/debug';
import { task } from 'ember-concurrency';
import calculatePosition from 'ember-basic-dropdown/utils/calculate-position';

export default class LocaleSelectorTrayComponent extends Component {
@service intl;
@service fetch;
@service media;

/**
* Tracks all the available locales.
*
* @memberof LocaleSelectorComponent
*/
@service language;
@tracked locales = [];

/**
* All available countries data.
*
* @memberof LocaleSelectorComponent
*/
@tracked countries = [];

/**
* The current locale in use.
*
* @memberof LocaleSelectorComponent
*/
@tracked currentLocale;

/**
Expand All @@ -41,7 +22,6 @@ export default class LocaleSelectorTrayComponent extends Component {

this.locales = this.intl.locales;
this.currentLocale = this.intl.primaryLocale;
this.loadAvailableCountries.perform();

// Check for locale change
this.intl.onLocaleChanged(() => {
Expand Down Expand Up @@ -85,28 +65,6 @@ export default class LocaleSelectorTrayComponent extends Component {
this.saveUserLocale.perform(selectedLocale);
}

/**
* Loads available countries asynchronously.
* @returns {void}
* @memberof LocaleSelectorComponent
* @method loadAvailableCountries
* @instance
* @task
* @generator
*/
@task *loadAvailableCountries() {
try {
this.countries = yield this.fetch.get(
'lookup/countries',
{ columns: ['name', 'cca2', 'flag', 'emoji', 'languages'] },
{ fromCache: true, expirationInterval: 1, expirationIntervalUnit: 'week' }
);
this.availableLocales = this._createAvailableLocaleMap();
} catch (error) {
debug(`Locale Error: ${error.message}`);
}
}

/**
* Saves the user's selected locale to the server.
* @param {string} locale - The user's selected locale.
Expand All @@ -120,45 +78,4 @@ export default class LocaleSelectorTrayComponent extends Component {
@task *saveUserLocale(locale) {
yield this.fetch.post('users/locale', { locale });
}

/**
* Creates a map of available locales.
* @private
* @returns {Object} - The map of available locales.
* @memberof LocaleSelectorComponent
* @method _createAvailableLocaleMap
* @instance
*/
_createAvailableLocaleMap() {
const localeMap = {};

for (let i = 0; i < this.locales.length; i++) {
const locale = this.locales.objectAt(i);

localeMap[locale] = this._findCountryDataForLocale(locale);
}

return localeMap;
}

/**
* Finds country data for a given locale.
* @private
* @param {string} locale - The locale to find country data for.
* @returns {Object|null} - The country data or null if not found.
* @memberof LocaleSelectorComponent
* @method _findCountryDataForLocale
* @instance
*/
_findCountryDataForLocale(locale) {
const localeCountry = locale.split('-')[1];
const country = this.countries.find((country) => country.cca2.toLowerCase() === localeCountry);

if (country) {
// get the language
country.language = Object.values(country.languages)[0];
}

return country;
}
}
1 change: 1 addition & 0 deletions addon/components/overlay.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export default class OverlayComponent extends Component {
isOpen: this.isOpen,
isMinimized: this.isMinimized,
isMaximized: this.isMaximized,
overlayNode: this.overlayNode,
};

@action setupComponent(element) {
Expand Down
26 changes: 14 additions & 12 deletions addon/components/pill.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -26,23 +26,25 @@
{{#if (has-block)}}
{{yield @resource}}
{{else}}
<div class="text-sm {{@titleClass}}">{{n-a @title this.resourceName}}</div>
<div class="text-sm {{@titleClass}}">{{or @title this.resourceName @titleFallback "-"}}</div>
{{#if @subtitle}}
<div class="text-xs text-gray-400 dark:text-gray-500 {{@subtitleClass}}">{{n-a @subtitle}}</div>
{{/if}}
{{yield @resource}}
{{/if}}
</div>
{{#if (has-block "tooltip")}}
<Attach::Tooltip @class="clean" @animation="scale" @placement={{or @tooltipPosition "top"}}>
<InputInfo>
{{yield @resource to="tooltip"}}
</InputInfo>
</Attach::Tooltip>
{{else if @tooltipComponent}}
<Attach::Tooltip @class="clean" @animation="scale" @placement={{or @tooltipPosition "top"}}>
{{component @tooltipComponent}}
</Attach::Tooltip>
{{/if}}
{{#unless @noTooltip}}
{{#if (has-block "tooltip")}}
<Attach::Tooltip @class="clean" @animation="scale" @placement={{or @tooltipPosition "top"}}>
<InputInfo>
{{yield @resource to="tooltip"}}
</InputInfo>
</Attach::Tooltip>
{{else if @tooltipComponent}}
<Attach::Tooltip @class="clean" @animation="scale" @placement={{or @tooltipPosition "top"}}>
{{component @tooltipComponent}}
</Attach::Tooltip>
{{/if}}
{{/unless}}
</a>
</div>
2 changes: 1 addition & 1 deletion addon/components/pill.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ export default class PillComponent extends Component {
/* eslint-disable ember/no-get */
get resourceName() {
const record = this.args.resource;
if (!record) return 'resource';
if (!record) return null;

return (
get(record, this.args.namePath ?? 'name') ??
Expand Down
90 changes: 90 additions & 0 deletions addon/utils/dom.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { getOwner } from '@ember/application';
import { DEBUG } from '@glimmer/env';
import { warn } from '@ember/debug';
import { schedule } from '@ember/runloop';
import { isArray } from '@ember/array';
import { all } from 'rsvp';
import requirejs from 'require';

Expand Down Expand Up @@ -166,3 +167,92 @@ export function waitForInsertedAndSized(getElOrEl, { timeoutMs = 4000 } = {}) {
}
});
}

/**
* Create a DOM element with declarative options.
*
* @param {string} tag
* @param {Object} [options]
* @param {string|string[]|Node|Node[]} [children]
* @returns {HTMLElement}
*/
export function createElement(tag, options = {}, children = null) {
const el = document.createElement(tag);

// ---------- Classes ----------
if (options.classNames) {
const classes = isArray(options.classNames) ? options.classNames : options.classNames.split(' ');
el.classList.add(...classes.filter(Boolean));
}

// ---------- Styles ----------
if (options.styles && typeof options.styles === 'object') {
Object.assign(el.style, options.styles);
}

// ---------- Attributes ----------
if (options.attrs && typeof options.attrs === 'object') {
for (const [key, value] of Object.entries(options.attrs)) {
if (value !== false && value != null) {
el.setAttribute(key, value === true ? '' : value);
}
}
}

// ---------- Dataset ----------
if (options.dataset && typeof options.dataset === 'object') {
for (const [key, value] of Object.entries(options.dataset)) {
el.dataset[key] = value;
}
}

// ---------- Event listeners ----------
if (options.on && typeof options.on === 'object') {
for (const [event, handler] of Object.entries(options.on)) {
if (typeof handler === 'function') {
el.addEventListener(event, handler);
}
}
}

// ---------- Text / HTML (exclusive) ----------
const hasText = options.text != null || options.innerText != null;
const hasHtml = options.html != null || options.innerHTML != null;

if (hasText && hasHtml) {
throw new Error('createElement: use either text OR html, not both.');
}

if (hasText) {
el.textContent = options.text ?? options.innerText;
} else if (hasHtml) {
el.innerHTML = options.html ?? options.innerHTML;
} else {
// ---------- Children ----------
const append = (child) => {
if (child == null) return;
if (Array.isArray(child)) return child.forEach(append);
if (child instanceof Node) el.appendChild(child);
else el.appendChild(document.createTextNode(String(child)));
};

append(children);
}

// ---------- Mount ----------
if (options.mount) {
let mountTarget = options.mount;

if (typeof mountTarget === 'string') {
mountTarget = document.querySelector(mountTarget);
}

if (mountTarget instanceof Element) {
mountTarget.appendChild(el);
} else {
console.warn('createElement: mount target not found', options.mount);
}
}

return el;
}
Loading