diff --git a/packages/@ember/-internals/glimmer/lib/component-managers/curly.ts b/packages/@ember/-internals/glimmer/lib/component-managers/curly.ts index 23d004c28f5..ab88e8ea278 100644 --- a/packages/@ember/-internals/glimmer/lib/component-managers/curly.ts +++ b/packages/@ember/-internals/glimmer/lib/component-managers/curly.ts @@ -28,7 +28,7 @@ import type { import type { Reference } from '@glimmer/reference'; import { childRefFor, createComputeRef, createPrimitiveRef, valueForRef } from '@glimmer/reference'; import { reifyPositional } from '@glimmer/runtime'; -import { EMPTY_ARRAY, unwrapTemplate } from '@glimmer/util'; +import { unwrapTemplate } from '@glimmer/utils'; import { beginTrackFrame, beginUntrackFrame, @@ -54,6 +54,7 @@ import { processComponentArgs } from '../utils/process-args'; export const ARGS = enumerableSymbol('ARGS'); export const HAS_BLOCK = enumerableSymbol('HAS_BLOCK'); +const EMPTY_ARRAY = []; export const DIRTY_TAG = Symbol('DIRTY_TAG'); export const IS_DISPATCHING_ATTRS = Symbol('IS_DISPATCHING_ATTRS'); diff --git a/packages/@ember/-internals/glimmer/lib/component-managers/mount.ts b/packages/@ember/-internals/glimmer/lib/component-managers/mount.ts index 068d30c8fbf..e0d7e9dd9d5 100644 --- a/packages/@ember/-internals/glimmer/lib/component-managers/mount.ts +++ b/packages/@ember/-internals/glimmer/lib/component-managers/mount.ts @@ -21,7 +21,7 @@ import type { Nullable } from '@ember/-internals/utility-types'; import { capabilityFlagsFrom } from '@glimmer/manager'; import type { Reference } from '@glimmer/reference'; import { createConstRef, valueForRef } from '@glimmer/reference'; -import { unwrapTemplate } from '@glimmer/util'; +import { unwrapTemplate } from '@glimmer/utils'; import type RuntimeResolver from '../resolver'; interface EngineState { diff --git a/packages/@ember/-internals/glimmer/lib/component-managers/outlet.ts b/packages/@ember/-internals/glimmer/lib/component-managers/outlet.ts index 4503ffcb43d..21912b6dda7 100644 --- a/packages/@ember/-internals/glimmer/lib/component-managers/outlet.ts +++ b/packages/@ember/-internals/glimmer/lib/component-managers/outlet.ts @@ -21,7 +21,7 @@ import { capabilityFlagsFrom } from '@glimmer/manager'; import type { Reference } from '@glimmer/reference'; import { createConstRef, valueForRef } from '@glimmer/reference'; import { EMPTY_ARGS } from '@glimmer/runtime'; -import { unwrapTemplate } from '@glimmer/util'; +import { unwrapTemplate } from '@glimmer/utils'; import type { DynamicScope } from '../renderer'; import type { OutletState } from '../utils/outlet'; diff --git a/packages/@ember/-internals/glimmer/lib/helpers/internal-helper.ts b/packages/@ember/-internals/glimmer/lib/helpers/internal-helper.ts index f474a052e6e..a49f497c7fa 100644 --- a/packages/@ember/-internals/glimmer/lib/helpers/internal-helper.ts +++ b/packages/@ember/-internals/glimmer/lib/helpers/internal-helper.ts @@ -1,7 +1,10 @@ import type { InternalOwner } from '@ember/-internals/owner'; import type { Helper, HelperDefinitionState } from '@glimmer/interfaces'; -import { setInternalHelperManager } from '@glimmer/manager'; +// import { setInternalHelperManager } from '@glimmer/manager'; export function internalHelper(helper: Helper): HelperDefinitionState { - return setInternalHelperManager(helper, {}); + return function () { + console.log('internal helper', this, [...arguments]); + return helper(...arguments); + } } diff --git a/packages/@ember/-internals/glimmer/lib/renderer.ts b/packages/@ember/-internals/glimmer/lib/renderer.ts index 1c9c0e44469..52736ac67fb 100644 --- a/packages/@ember/-internals/glimmer/lib/renderer.ts +++ b/packages/@ember/-internals/glimmer/lib/renderer.ts @@ -4,6 +4,7 @@ import type { InternalOwner } from '@ember/-internals/owner'; import { getOwner } from '@ember/-internals/owner'; import { guidFor } from '@ember/-internals/utils'; import { getViewElement, getViewId } from '@ember/-internals/views'; +import { renderComponent, runDestructors } from '@lifeart/gxt'; import { assert } from '@ember/debug'; import { _backburner, _getCurrentRunLoop } from '@ember/runloop'; import { destroy } from '@glimmer/destroyable'; @@ -31,14 +32,14 @@ import { createConstRef, UNDEFINED_REFERENCE, valueForRef } from '@glimmer/refer import type { CurriedValue } from '@glimmer/runtime'; import { clientBuilder, - curry, + // curry, DOMChanges, DOMTreeConstruction, inTransaction, renderMain, runtimeContext, } from '@glimmer/runtime'; -import { unwrapTemplate } from '@glimmer/util'; +import { unwrapTemplate } from '@glimmer/utils'; import { CURRENT_TAG, validateTag, valueForTag } from '@glimmer/validator'; import type { SimpleDocument, SimpleElement, SimpleNode } from '@simple-dom/interface'; import RSVP from 'rsvp'; @@ -64,6 +65,23 @@ export interface View { [BOUNDS]: Bounds | null; } +function curry( + type: T, + spec: object | string | any, + owner: any, + args: any | null, + resolved = false +) { + console.log('curry'); + return { + type, + spec, + owner, + args, + resolved, + }; +} + export class DynamicScope implements GlimmerDynamicScope { constructor(public view: View | null, public outletState: Reference) {} @@ -129,13 +147,13 @@ class RootState { constructor( public root: Component | OutletView, public runtime: RuntimeContext, - context: CompileTimeCompilationContext, + _context: CompileTimeCompilationContext, owner: InternalOwner, template: Template, self: Reference, parentElement: SimpleElement, - dynamicScope: DynamicScope, - builder: IBuilder + _dynamicScope: DynamicScope, + _builder: IBuilder ) { assert( `You cannot render \`${valueForRef(self)}\` without a template.`, @@ -146,23 +164,16 @@ class RootState { this.result = undefined; this.destroyed = false; - this.render = errorLoopTransaction(() => { - let layout = unwrapTemplate(template).asLayout(); - - let iterator = renderMain( - runtime, - context, - owner, - self, - builder(runtime.env, { element: parentElement, nextSibling: null }), - layout, - dynamicScope - ); - - let result = (this.result = iterator.sync()); + // console.log(layout); - // override .render function after initial render - this.render = errorLoopTransaction(() => result.rerender({ alwaysRevalidate: false })); + this.render = errorLoopTransaction(() => { + let layout = unwrapTemplate(template).asLayout().compile(); + const layoutInstance = new layout(this); + // @ts-expect-error fine + this.result = renderComponent(layoutInstance, parentElement, owner); + this.render = errorLoopTransaction(() => { + // fine + }); }); } @@ -195,7 +206,11 @@ class RootState { */ - inTransaction(env, () => destroy(result!)); + inTransaction(env, () => { + // @ts-expect-error foo-bar + runDestructors(result.ctx); + destroy(result!); + }); } } } @@ -370,6 +385,7 @@ export class Renderer { // renderer HOOKS appendOutletView(view: OutletView, target: SimpleElement): void { + console.log('appendOutletView', view, target); let definition = createRootOutlet(view); this._appendDefinition( view, diff --git a/packages/@ember/-internals/glimmer/lib/templates/empty.ts b/packages/@ember/-internals/glimmer/lib/templates/empty.ts index f5665d5f8b0..38d472ce04e 100644 --- a/packages/@ember/-internals/glimmer/lib/templates/empty.ts +++ b/packages/@ember/-internals/glimmer/lib/templates/empty.ts @@ -1,5 +1,4 @@ -import { precompileTemplate } from '@ember/template-compilation'; -export default precompileTemplate('', { - moduleName: 'packages/@ember/-internals/glimmer/lib/templates/empty.hbs', - strictMode: true, -}); +import { hbs } from '@lifeart/gxt'; +export default function emptyTemplate() { + return hbs``; +}; diff --git a/packages/@ember/-internals/glimmer/lib/templates/outlet-helper-component.gts b/packages/@ember/-internals/glimmer/lib/templates/outlet-helper-component.gts new file mode 100644 index 00000000000..70bad9c775f --- /dev/null +++ b/packages/@ember/-internals/glimmer/lib/templates/outlet-helper-component.gts @@ -0,0 +1,64 @@ +import { Component } from '@lifeart/gxt'; + +interface State { + outlets: { + main: State | undefined, + }, + render: { + template(): () => unknown, + controller: unknown, + name: string, + } +} + +export default class OutletHelper extends Component { + get state() { + return this.args.state().outlets.main || this.args.state(); + } + get nextState() { + return () => { + return this.hasNext; + } + } + get hasNext() { + return this.state.outlets.main; + } + get canRender() { + return !!this?.state?.render; + } + get MyComponent() { + + const state = this.state; + const render = state.render; + const tpl = render.template(); + const args = { + get model() { + return render.model; + } + } + if (tpl.instance) { + return tpl.instance.template; + } + render.controller['args'] = args; + const tplComponentInstance = new tpl(args); + tplComponentInstance.template = tplComponentInstance.template.bind(render.controller); + // we need to provide stable refs here to avoid re-renders + tpl.instance = tplComponentInstance; + return tplComponentInstance.template; + } + get model() { + const state = this.state; + const render = state.render; + return render.model; + } + +} diff --git a/packages/@ember/-internals/glimmer/lib/templates/outlet.ts b/packages/@ember/-internals/glimmer/lib/templates/outlet.ts index 803be28628f..bd96fc3fc94 100644 --- a/packages/@ember/-internals/glimmer/lib/templates/outlet.ts +++ b/packages/@ember/-internals/glimmer/lib/templates/outlet.ts @@ -1,10 +1,15 @@ import { precompileTemplate } from '@ember/template-compilation'; +import { hbs } from '@lifeart/gxt'; import { outletHelper } from '../syntax/outlet'; +import Outlet from './outlet-helper-component'; -export default precompileTemplate(`{{component (outletHelper)}}`, { - moduleName: 'packages/@ember/-internals/glimmer/lib/templates/outlet.hbs', - strictMode: true, - scope() { - return { outletHelper }; - }, -}); +export default (owner) => { + console.log('outlet factory', owner); + + return function(args) { + console.log('outlet', this, owner, ...arguments); + return hbs`{{#let (component Outlet state=(args.state)) as |Outlet|}} +
[main outlet template]
+ {{/let}}`; + } +} diff --git a/packages/@ember/-internals/glimmer/lib/templates/root.ts b/packages/@ember/-internals/glimmer/lib/templates/root.ts index bb4736d9214..e7981edcdc4 100644 --- a/packages/@ember/-internals/glimmer/lib/templates/root.ts +++ b/packages/@ember/-internals/glimmer/lib/templates/root.ts @@ -1,5 +1,22 @@ -import { precompileTemplate } from '@ember/template-compilation'; -export default precompileTemplate(`{{component this}}`, { - moduleName: 'packages/@ember/-internals/glimmer/lib/templates/root.hbs', - strictMode: true, -}); +import { hbs, $_fin } from '@lifeart/gxt'; +export default function(owner) { + console.log('root-template init', owner); + return function(rootState) { + // console.log('root-template - render', [this], [...arguments]); + // temp1.root.template + // console.log(...arguments); + // return function() { + // console.log(...arguments); + // return $_fin([...rootState.root.template()], this); + // } + // debugger; + const state = rootState.root.ref; + console.log('rootState', state); + return hbs` + {{log 'root-template-create' this rootState}} + {{#let (component rootState.root.template state=state root=true) as |Layout|}} + + {{/let}} + `; + } +} diff --git a/packages/@ember/-internals/glimmer/lib/views/outlet.ts b/packages/@ember/-internals/glimmer/lib/views/outlet.ts index ced2115585d..935d4cd256a 100644 --- a/packages/@ember/-internals/glimmer/lib/views/outlet.ts +++ b/packages/@ember/-internals/glimmer/lib/views/outlet.ts @@ -8,12 +8,14 @@ import { assert } from '@ember/debug'; import { schedule } from '@ember/runloop'; import type { Template, TemplateFactory } from '@glimmer/interfaces'; import type { Reference } from '@glimmer/reference'; -import { createComputeRef, updateRef } from '@glimmer/reference'; +import { cellFor } from '@lifeart/gxt'; +// import { reference } from '@lifeart/gxt/glimmer-compatibility'; import { consumeTag, createTag, dirtyTag } from '@glimmer/validator'; import type { SimpleElement } from '@simple-dom/interface'; import type { OutletDefinitionState } from '../component-managers/outlet'; import type { Renderer } from '../renderer'; import type { OutletState } from '../utils/outlet'; +// const { createComputeRef, updateRef } = reference; export interface BootEnvironment { hasDOM: boolean; @@ -46,6 +48,7 @@ export default class OutletView { application: InternalOwner; template: TemplateFactory; }): OutletView { + console.log('outlet-view create', options); let { environment: _environment, application: namespace, template: templateFactory } = options; let owner = getOwner(options); assert('OutletView is unexpectedly missing an owner', owner); @@ -75,17 +78,15 @@ export default class OutletView { template, }, }; + console.log('outletStateTag', outletStateTag); - let ref = (this.ref = createComputeRef( - () => { - consumeTag(outletStateTag); - return outletState; - }, - (state: OutletState) => { - dirtyTag(outletStateTag); - outletState.outlets['main'] = state; - } - )); + cellFor(outletState.outlets, 'main'); + + let ref = (this.ref = outletState); + + console.log('ref', ref); + + // ref.compute(); this.state = { ref, @@ -117,7 +118,10 @@ export default class OutletView { } setOutletState(state: OutletState): void { - updateRef(this.ref, state); + // debugger; + // @todo - fix re-renders + this.ref.outlets['main'] = state; + // updateRef(this.ref, state); } destroy(): void { diff --git a/packages/@ember/-internals/metal/lib/decorator.ts b/packages/@ember/-internals/metal/lib/decorator.ts index d2bf0ff2ccb..d5d498608fa 100644 --- a/packages/@ember/-internals/metal/lib/decorator.ts +++ b/packages/@ember/-internals/metal/lib/decorator.ts @@ -76,7 +76,7 @@ export abstract class ComputedDescriptor { abstract set(obj: object, keyName: string, value: any | null | undefined): any | null | undefined; } -export let COMPUTED_GETTERS: WeakSet<() => unknown>; +export let COMPUTED_GETTERS: WeakSet<() => unknown> = new WeakSet(); if (DEBUG) { COMPUTED_GETTERS = new WeakSet(); diff --git a/packages/@ember/-internals/metal/lib/tracked.ts b/packages/@ember/-internals/metal/lib/tracked.ts index f5f696997f5..0c5cac9c939 100644 --- a/packages/@ember/-internals/metal/lib/tracked.ts +++ b/packages/@ember/-internals/metal/lib/tracked.ts @@ -2,13 +2,44 @@ import { meta as metaFor } from '@ember/-internals/meta'; import { isEmberArray } from '@ember/array/-internals'; import { assert } from '@ember/debug'; import { DEBUG } from '@glimmer/env'; -import { consumeTag, dirtyTagFor, tagFor, trackedData } from '@glimmer/validator'; +// import { consumeTag, dirtyTagFor, tagFor, trackedData } from '@glimmer/validator'; +import { validator } from '@lifeart/gxt/glimmer-compatibility'; +import { cellFor } from '@lifeart/gxt'; + import type { ElementDescriptor } from '..'; import { CHAIN_PASS_THROUGH } from './chain-tags'; import type { ExtendedMethodDecorator, DecoratorPropertyDescriptor } from './decorator'; import { COMPUTED_SETTERS, isElementDescriptor, setClassicDecorator } from './decorator'; import { SELF_TAG } from './tags'; +const { + consumeTag, dirtyTagFor, tagFor +} = validator; + + +function trackedData(key, initializer) { + let values = /* @__PURE__ */ new WeakMap(); + let hasInitializer = typeof initializer === "function"; + function getter(self) { + // consumeTag(cellFor(self, key)); + let value; + if (hasInitializer && !values.has(self)) { + value = initializer.call(self); + values.set(self, value); + } else { + value = values.get(self); + } + return value; + } + function setter(self, value) { + dirtyTagFor(self, key); + values.set(self, value); + } + return { + getter, + setter + }; +} /** @decorator @private diff --git a/packages/@ember/-internals/package.json b/packages/@ember/-internals/package.json index 68ee335bd3f..84b15707e4f 100644 --- a/packages/@ember/-internals/package.json +++ b/packages/@ember/-internals/package.json @@ -64,6 +64,7 @@ "expect-type": "^0.15.0", "internal-test-helpers": "workspace:*", "router_js": "^8.0.5", - "rsvp": "^4.8.5" + "rsvp": "^4.8.5", + "@lifeart/gxt": "^0.0.49" } } diff --git a/packages/@ember/routing/route.ts b/packages/@ember/routing/route.ts index 62b51e9c6c8..46b5b082208 100644 --- a/packages/@ember/routing/route.ts +++ b/packages/@ember/routing/route.ts @@ -19,7 +19,7 @@ import type { AnyFn } from '@ember/-internals/utility-types'; import Controller from '@ember/controller'; import type { ControllerQueryParamType } from '@ember/controller'; import { assert, info, isTesting } from '@ember/debug'; -import { DEPRECATIONS, deprecateUntil } from '@ember/-internals/deprecations'; +// import { DEPRECATIONS, deprecateUntil } from '@ember/-internals/deprecations'; import EngineInstance from '@ember/engine/instance'; import { dependentKeyCompat } from '@ember/object/compat'; import { once } from '@ember/runloop'; @@ -1256,11 +1256,11 @@ class Route extends EmberObject.extend(ActionHandler, Evented) if (ENV._NO_IMPLICIT_ROUTE_MODEL) { return; } - deprecateUntil( - `The implicit model loading behavior for routes is deprecated. ` + - `Please define an explicit model hook for ${this.fullRouteName}.`, - DEPRECATIONS.DEPRECATE_IMPLICIT_ROUTE_MODEL - ); + // deprecateUntil( + // `The implicit model loading behavior for routes is deprecated. ` + + // `Please define an explicit model hook for ${this.fullRouteName}.`, + // DEPRECATIONS.DEPRECATE_IMPLICIT_ROUTE_MODEL + // ); const store = 'store' in this ? this.store : get(this, '_store'); assert('Expected route to have a store with a find method', isStoreLike(store)); diff --git a/packages/@ember/routing/router.ts b/packages/@ember/routing/router.ts index f41a58d6a4e..5cef91f49d6 100644 --- a/packages/@ember/routing/router.ts +++ b/packages/@ember/routing/router.ts @@ -675,6 +675,7 @@ class EmberRouter extends EmberObject.extend(Evented) implements Evented { instance.didCreateRootView(this._toplevelView as any); } } else { + // here we need to figure out how to provide atomic reactivity per outlet level this._toplevelView.setOutletState(root); } } diff --git a/packages/demo/compat/compile.ts b/packages/demo/compat/compile.ts new file mode 100644 index 00000000000..77b5a8d4110 --- /dev/null +++ b/packages/demo/compat/compile.ts @@ -0,0 +1,8 @@ +export function precompileTemplate() { + console.log('precompile template', ...arguments); + return {}; +} +export default function templateCompilation() { + console.log('templateCompilation', ...arguments); + return {}; +} diff --git a/packages/demo/compat/deprecate.ts b/packages/demo/compat/deprecate.ts new file mode 100644 index 00000000000..600a3aab254 --- /dev/null +++ b/packages/demo/compat/deprecate.ts @@ -0,0 +1,4 @@ +export function deprecateUntil() { + +} +export const DEPRECATIONS = {}; diff --git a/packages/demo/compat/glimmer-application.ts b/packages/demo/compat/glimmer-application.ts new file mode 100644 index 00000000000..40e0f69e365 --- /dev/null +++ b/packages/demo/compat/glimmer-application.ts @@ -0,0 +1,6 @@ +export function capabilities() { + return {}; +} +export function setComponentManager() { + console.log('setComponentManager', ...arguments); +} diff --git a/packages/demo/compat/glimmer-util.ts b/packages/demo/compat/glimmer-util.ts new file mode 100644 index 00000000000..fde865d533a --- /dev/null +++ b/packages/demo/compat/glimmer-util.ts @@ -0,0 +1,14 @@ +export function unwrapTemplate(tpl) { + return { + asLayout(){ + return { + compile() { + // debugger; + console.log('as-layout compile', ...arguments); + return tpl; + } + }; + } + }; +}; + diff --git a/packages/demo/index.html b/packages/demo/index.html new file mode 100644 index 00000000000..458e7f7f465 --- /dev/null +++ b/packages/demo/index.html @@ -0,0 +1,25 @@ + + + + + + + Vite + TS + + + +
+
+ + + diff --git a/packages/demo/package.json b/packages/demo/package.json new file mode 100644 index 00000000000..0e853503985 --- /dev/null +++ b/packages/demo/package.json @@ -0,0 +1,53 @@ +{ + "name": "demo", + "private": true, + "type": "module", + "scripts": { + "dev": "vite" + }, + "dependencies": { + "@ember/-internals": "workspace:*", + "@ember/application": "workspace:*", + "@ember/array": "workspace:*", + "@ember/canary-features": "workspace:*", + "@ember/component": "workspace:*", + "@ember/controller": "workspace:*", + "@ember/debug": "workspace:*", + "ember": "workspace:*", + "@ember/destroyable": "workspace:*", + "@ember/engine": "workspace:*", + "@ember/enumerable": "workspace:*", + "@ember/helper": "workspace:*", + "@ember/instrumentation": "workspace:*", + "@ember/modifier": "workspace:*", + "@ember/object": "workspace:*", + "@ember/owner": "workspace:*", + "@ember/routing": "workspace:*", + "@ember/runloop": "workspace:*", + "@ember/service": "workspace:*", + "@ember/template": "workspace:*", + "@ember/template-compilation": "workspace:*", + "@ember/template-factory": "workspace:*", + "@ember/test": "workspace:*", + "@ember/utils": "workspace:*", + "@ember/version": "workspace:*", + "@glimmer/destroyable": "0.92.0", + "@glimmer/env": "^0.1.7", + "@glimmer/manager": "0.92.0", + "@glimmer/owner": "0.92.0", + "@glimmer/runtime": "0.92.0", + "@glimmer/tracking": "workspace:*", + "@glimmer/util": "0.92.0", + "@glimmer/validator": "0.92.0", + "@lifeart/gxt": "^0.0.49", + "backburner.js": "^2.7.0", + "dag-map": "^2.0.2", + "ember-template-compiler": "workspace:*", + "ember-testing": "workspace:*", + "expect-type": "^0.15.0", + "internal-test-helpers": "workspace:*", + "router_js": "^8.0.5", + "rsvp": "^4.8.5", + "vite": "^5.0.10" + } +} diff --git a/packages/demo/src/components/Application.gts b/packages/demo/src/components/Application.gts new file mode 100644 index 00000000000..a84f7ea5986 --- /dev/null +++ b/packages/demo/src/components/Application.gts @@ -0,0 +1,15 @@ +import { Component } from '@lifeart/gxt'; + +export default class ApplicationTemplate extends Component { + +} diff --git a/packages/demo/src/components/Main.gts b/packages/demo/src/components/Main.gts new file mode 100644 index 00000000000..57f3866b2af --- /dev/null +++ b/packages/demo/src/components/Main.gts @@ -0,0 +1,15 @@ +import { Component } from '@lifeart/gxt'; + +export default class MainTemplate extends Component { + +} diff --git a/packages/demo/src/components/Profile.gts b/packages/demo/src/components/Profile.gts new file mode 100644 index 00000000000..da0648639a4 --- /dev/null +++ b/packages/demo/src/components/Profile.gts @@ -0,0 +1,12 @@ +import { Component } from '@lifeart/gxt'; + +export default class ProfileTemplate extends Component { + +} diff --git a/packages/demo/src/config/application.ts b/packages/demo/src/config/application.ts new file mode 100644 index 00000000000..a85dc1dd8d4 --- /dev/null +++ b/packages/demo/src/config/application.ts @@ -0,0 +1,11 @@ +import EmberApplication from '@ember/application'; +import ENV from './env'; +import Resolver from './resolver'; + +export default class App extends EmberApplication { + rootElement = ENV.rootElement; + autoboot = ENV.autoboot; + modulePrefix = ENV.modulePrefix; + podModulePrefix = `${ENV.modulePrefix}/pods`; + Resolver = Resolver; +} diff --git a/packages/demo/src/config/breakpoints.ts b/packages/demo/src/config/breakpoints.ts new file mode 100644 index 00000000000..6429c9cfc1b --- /dev/null +++ b/packages/demo/src/config/breakpoints.ts @@ -0,0 +1,6 @@ +export default { + mobile: '(max-width: 767px)', + tablet: '(min-width: 768px) and (max-width: 991px)', + desktop: '(min-width: 992px) and (max-width: 1200px)', + jumbo: '(min-width: 1201px)', +}; diff --git a/packages/demo/src/config/class-factory.ts b/packages/demo/src/config/class-factory.ts new file mode 100644 index 00000000000..4fc4768df25 --- /dev/null +++ b/packages/demo/src/config/class-factory.ts @@ -0,0 +1,11 @@ +export default function classFactory(klass) { + return { + create(injections) { + if (typeof klass.extend === 'function') { + return klass.extend(injections); + } else { + return klass; + } + }, + }; +} diff --git a/packages/demo/src/config/env.ts b/packages/demo/src/config/env.ts new file mode 100644 index 00000000000..fdf5fadc5ad --- /dev/null +++ b/packages/demo/src/config/env.ts @@ -0,0 +1,29 @@ +import packageJSON from '../../package.json'; + +function config(environment: 'production' | 'development') { + const ENV = { + modulePrefix: packageJSON.name, + environment, + rootElement: '#app', + autoboot: false, + rootURL: '/', + locationType: 'history', // here is the change + EmberENV: { + FEATURES: {}, + EXTEND_PROTOTYPES: false, + _JQUERY_INTEGRATION: false, + _APPLICATION_TEMPLATE_WRAPPER: false, + _DEFAULT_ASYNC_OBSERVERS: true, + _TEMPLATE_ONLY_GLIMMER_COMPONENTS: true, + }, + APP: { + version: packageJSON.version, + globalName: 'MyApp', + }, + }; + + return ENV; +} + +const ENV = config(import.meta.env.MODE as 'production' | 'development'); +export default ENV; diff --git a/packages/demo/src/config/helpers.ts b/packages/demo/src/config/helpers.ts new file mode 100644 index 00000000000..4825cd12de3 --- /dev/null +++ b/packages/demo/src/config/helpers.ts @@ -0,0 +1,34 @@ +// import EmberGlimmerComponentManager from 'ember-component-manager'; +import Component from '@glimmer/component'; +import { setOwner, getOwner } from '@ember/owner'; +import { capabilities } from '@ember/component'; +import { setComponentManager } from '@ember/component'; +import { Ember } from '../../types/global'; +import config from './env'; + +class CustomComponentManager { + constructor() { + debugger; + } + capabilities = capabilities('3.13'); + + createComponent( + ...args: Parameters + ) { + const component = super.createComponent(...args); + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + setOwner(component, getOwner(this)!); + + return component; + } +} + +export function setupApplicationGlobals(EmberNamespace: Ember) { + setComponentManager((owner) => { + return new CustomComponentManager(owner); + }, Component); + + window.EmberENV = config.EmberENV; + window._Ember = EmberNamespace; + window.Ember = EmberNamespace; +} diff --git a/packages/demo/src/config/initializer.ts b/packages/demo/src/config/initializer.ts new file mode 100644 index 00000000000..fe3b878d443 --- /dev/null +++ b/packages/demo/src/config/initializer.ts @@ -0,0 +1,43 @@ +import ENV from './env'; +import registry from './registry'; +import type ApplicationClass from '@ember/application'; +import type RouteClass from './router'; +import { default as initializer } from '../initializers/logger'; +import { default as logger } from '../instance-initializers/logger'; +// import { default as modalDialog } from '../instance-initializers/ember-modal-dialog'; +// import { default as emberDataInitializer } from '../initializers/ember-data'; +// import { default as emberResponsive } from '../initializers/ember-responsive'; +import breakpoints from './breakpoints'; + +export function init( + Application: typeof ApplicationClass, + Router: typeof RouteClass +) { + // Init initializers + Application.initializer(initializer); + // Application.initializer(emberDataInitializer); + // Application.initializer(emberResponsive); + + // Init instance initializers + Application.instanceInitializer(logger); + // Application.instanceInitializer(modalDialog); + + const app = Application.create({ + name: ENV.modulePrefix, + version: ENV.APP.version, + }); + + const registryObjects = registry(); + console.table(registryObjects); + + Object.keys(registryObjects).forEach((key) => { + const value = registryObjects[key]; + app.register(key, value); + }); + + app.register('config:environment', ENV); + app.register('router:main', Router); + app.register('breakpoints:main', breakpoints); + + return app; +} diff --git a/packages/demo/src/config/inspector.ts b/packages/demo/src/config/inspector.ts new file mode 100644 index 00000000000..e98432dfc58 --- /dev/null +++ b/packages/demo/src/config/inspector.ts @@ -0,0 +1,92 @@ +import * as computed from '@ember/object/computed'; +import * as runloop from '@ember/runloop'; +import * as metal from '@ember/-internals/metal'; +import * as inst from '@ember/instrumentation'; +import * as view from '@ember/-internals/views'; +import * as ref from '@glimmer/reference'; +import * as val from '@glimmer/validator'; + +let define = window.define, + requireModule = window.requireModule; +if (typeof define !== 'function' || typeof requireModule !== 'function') { + (function () { + const registry = { + ember: window.Ember, + }, + seen = {}; + + define = function (name, deps, callback) { + if (arguments.length < 3) { + callback = deps; + deps = []; + } + registry[name] = { deps, callback }; + }; + + requireModule = function (name) { + if (name === '@ember/object/computed') { + return computed; + } + if (name === '@ember/runloop') { + return runloop; + } + if (name === '@ember/-internals/metal') { + return metal; + } + if (name === '@ember/instrumentation') { + return inst; + } + if (name === '@ember/-internals/views') { + return view; + } + + if (name === '@glimmer/reference') { + return ref; + } + if (name === '@glimmer/validator') { + return val; + } + + if (name === 'ember') { + return { + default: window.Ember, + }; + } + + if (seen[name]) { + return seen[name]; + } + + const mod = registry[name]; + if (!mod) { + throw new Error(`Module: '${name}' not found.`); + } + + seen[name] = {}; + + const deps = mod.deps; + const callback = mod.callback; + const reified = []; + let exports; + + for (let i = 0, l = deps.length; i < l; i++) { + if (deps[i] === 'exports') { + reified.push((exports = {})); + } else { + reified.push(requireModule(deps[i])); + } + } + + const value = callback.apply(this, reified); + seen[name] = exports || value; + return seen[name]; + }; + + define.registry = registry; + define.seen = seen; + })(); +} +requireModule.entries = define.registry; + +window.define = define; +window.requireModule = requireModule; diff --git a/packages/demo/src/config/registry.ts b/packages/demo/src/config/registry.ts new file mode 100644 index 00000000000..1ac27fbc062 --- /dev/null +++ b/packages/demo/src/config/registry.ts @@ -0,0 +1,134 @@ +import type { IRegistry } from './utils'; +// import { DEBUG } from '@glimmer/env'; +/* imported routes */ +import { ApplicationRoute } from '../routes/application'; +import ApplicationTemplate from './../components/Application'; +import ProfileTemplate from './../components/Profile'; +import MainTemplate from './../components/Main'; +// import LoginRoute from '../routes/login'; +// import LogoutRoute from '../routes/logout'; +// import { MainRoute } from '../routes/main'; +import ProfileRoute from '../routes/profile'; + +/* imported authenticators */ +// import CustomAuthenticator from '../authenticators/custom'; + +/* imported controllers */ +import { ApplicationController } from '@/controllers/application'; +import { ProfileController } from '@/controllers/profile'; +// import { LoginController } from '@/controllers/login'; + +/* imported templates */ +// import ApplicationTemplate from '@/templates/application.hbs'; +// import AboutTemplate from '@/templates/about.hbs'; +// import LoginTemplate from '@/templates/login.hbs'; + +/* imported services */ +// import DateService from '@/services/date'; + +/* imported components */ +// import HelloWorld from '@/components/HelloWorld'; +// import Button from '@/components/Button'; +// import Header from '@/components/Header'; +// import Footer from '@/components/Footer'; +// import Hot from '@/components/Hot'; +/* imported helpers */ +// import MemoryUsage from '@/helpers/memory-usage'; +// import IsDev from '@/helpers/is-dev'; + +/* imported modifiers */ +// import ClickTracker from '@/modifiers/click-tracker'; + +// ember-data debug adapter +// import StoreService from '@/services/store'; +/* ember-data stuff */ +// import Pet from '@/models/pet'; +// import Person from '@/models/person'; + +const InitialRegistry = { + // 'service:store': StoreService, + // 'model:pet': Pet, + // 'model:person': Person, + // debug ember-data adapter + // 'authenticator:custom': CustomAuthenticator, + // 'service:date': DateService, + 'controller:application': ApplicationController, + 'controller:profile': ProfileController, + // 'controller:login': LoginController, + 'route:application': ApplicationRoute, + 'route:profile': ProfileRoute, + + // 'route:login': LoginRoute, + // 'route:logout': LogoutRoute, + 'template:main': (owner) => { + console.log('template: main', owner); + return function() { + console.log('template: main - init', ...arguments); + + // debugger; + return MainTemplate; + } + }, + 'template:application': (owner) => { + console.log('template: application', owner); + return function() { + console.log('template: application - init', ...arguments); + + // debugger; + return ApplicationTemplate; + } + }, + 'template:profile': (owner) => { + return function() { + return ProfileTemplate; + } + }, + // 'template:about': AboutTemplate, + // 'template:login': LoginTemplate, + + // 'component:hello-world': HelloWorld, + // 'component:button': Button, + // 'component:header': Header, + // 'component:footer': Footer, + + // 'component:hot': DEBUG ? Hot : null, + + // 'helper:memory-usage': MemoryUsage as unknown as () => string, // glint fix + // 'helper:is-dev': IsDev, + + // 'modifier:click-tracker': ClickTracker, + + /* embroider compatibility */ + + 'helper:ensure-safe-component': function (a: string) { + return a; + }, + 'helper:macroCondition': function (a: string) { + // ember-bootstrap compat + if (typeof a === 'boolean') { + return a; + } + console.log('macroCondition', a); + return a; + }, + 'helper:macroGetOwnConfig': function (a: string) { + // ember-bootstrap compat + if (a === 'isNotBS5') { + return false; + } + if (a === 'isBS5') { + return true; + } + if (a === 'isBS4') { + return false; + } + console.log('macroGetOwnConfig', a); + return a; + }, +}; + +function registry(): IRegistry { + return InitialRegistry; +} + +export default registry; diff --git a/packages/demo/src/config/resolver.ts b/packages/demo/src/config/resolver.ts new file mode 100644 index 00000000000..1d44f71f848 --- /dev/null +++ b/packages/demo/src/config/resolver.ts @@ -0,0 +1,548 @@ +/* globals requirejs, require */ + +import { assert, deprecate, warn } from '@ember/debug'; +import EmberObject from '@ember/object'; +import { dasherize, classify, underscore } from './string'; +import { DEBUG } from '@glimmer/env'; +import classFactory from './class-factory'; + +import { getOwner } from '@ember/owner'; + +// if (typeof requirejs.entries === 'undefined') { +// requirejs.entries = requirejs._eak_seen; +// } + +export class ModuleRegistry { + constructor(entries) { + this._entries = entries || {}; + } + moduleNames() { + return Object.keys(this._entries); + } + has(moduleName) { + return moduleName in this._entries; + } + get(...args) { + return require(...args); + } +} + +/** + * This module defines a subclass of Ember.DefaultResolver that adds two + * important features: + * + * 1) The resolver makes the container aware of es6 modules via the AMD + * output. The loader's _moduleEntries is consulted so that classes can be + * resolved directly via the module loader, without needing a manual + * `import`. + * 2) is able to provide injections to classes that implement `extend` + * (as is typical with Ember). + */ +class Resolver extends EmberObject { + static moduleBasedResolver = true; + moduleBasedResolver = true; + + _deprecatedPodModulePrefix = false; + _normalizeCache = Object.create(null); + + /** + A listing of functions to test for moduleName's based on the provided + `parsedName`. This allows easy customization of additional module based + lookup patterns. + + @property moduleNameLookupPatterns + @returns {Ember.Array} + */ + moduleNameLookupPatterns = [ + this.podBasedModuleName, + this.podBasedComponentsInSubdir, + this.mainModuleName, + this.defaultModuleName, + this.nestedColocationComponentModuleName, + ]; + + constructor() { + super(...arguments); + + if (!this._moduleRegistry) { + this._moduleRegistry = new ModuleRegistry(); + } + + this.pluralizedTypes = this.pluralizedTypes || Object.create(null); + + if (!this.pluralizedTypes.config) { + this.pluralizedTypes.config = 'config'; + } + } + + makeToString(factory, fullName) { + return '' + this.namespace.modulePrefix + '@' + fullName + ':'; + } + + shouldWrapInClassFactory(/* module, parsedName */) { + return false; + } + + parseName(fullName) { + if (fullName.parsedName === true) { + return fullName; + } + + let prefix, type, name; + let fullNameParts = fullName.split('@'); + + if (fullNameParts.length === 3) { + if (fullNameParts[0].length === 0) { + // leading scoped namespace: `@scope/pkg@type:name` + prefix = `@${fullNameParts[1]}`; + let prefixParts = fullNameParts[2].split(':'); + type = prefixParts[0]; + name = prefixParts[1]; + } else { + // interweaved scoped namespace: `type:@scope/pkg@name` + prefix = `@${fullNameParts[1]}`; + type = fullNameParts[0].slice(0, -1); + name = fullNameParts[2]; + } + + if (type === 'template:components') { + name = `components/${name}`; + type = 'template'; + } + } else if (fullNameParts.length === 2) { + let prefixParts = fullNameParts[0].split(':'); + + if (prefixParts.length === 2) { + if (prefixParts[1].length === 0) { + type = prefixParts[0]; + name = `@${fullNameParts[1]}`; + } else { + prefix = prefixParts[1]; + type = prefixParts[0]; + name = fullNameParts[1]; + } + } else { + let nameParts = fullNameParts[1].split(':'); + + prefix = fullNameParts[0]; + type = nameParts[0]; + name = nameParts[1]; + } + + if (type === 'template' && prefix.lastIndexOf('components/', 0) === 0) { + name = `components/${name}`; + prefix = prefix.slice(11); + } + } else { + fullNameParts = fullName.split(':'); + type = fullNameParts[0]; + name = fullNameParts[1]; + } + + let fullNameWithoutType = name; + let namespace = this.namespace; + let root = namespace; + + return { + parsedName: true, + fullName: fullName, + prefix: prefix || this.prefix({ type: type }), + type: type, + fullNameWithoutType: fullNameWithoutType, + name: name, + root: root, + resolveMethodName: 'resolve' + classify(type), + }; + } + + resolveOther(parsedName) { + assert('`modulePrefix` must be defined', this.namespace.modulePrefix); + + let normalizedModuleName = this.findModuleName(parsedName); + + if (normalizedModuleName) { + let defaultExport = this._extractDefaultExport( + normalizedModuleName, + parsedName + ); + + if (defaultExport === undefined) { + throw new Error( + ` Expected to find: '${parsedName.fullName}' within '${normalizedModuleName}' but got 'undefined'. Did you forget to 'export default' within '${normalizedModuleName}'?` + ); + } + + if (this.shouldWrapInClassFactory(defaultExport, parsedName)) { + defaultExport = classFactory(defaultExport); + } + + return defaultExport; + } + } + + normalize(fullName) { + return ( + this._normalizeCache[fullName] || + (this._normalizeCache[fullName] = this._normalize(fullName)) + ); + } + + resolve(fullName) { + let parsedName = this.parseName(fullName); + let resolveMethodName = parsedName.resolveMethodName; + let resolved; + + if (typeof this[resolveMethodName] === 'function') { + resolved = this[resolveMethodName](parsedName); + } + + if (resolved == null) { + resolved = this.resolveOther(parsedName); + } + + return resolved; + } + + _normalize(fullName) { + // A) Convert underscores to dashes + // B) Convert camelCase to dash-case, except for components (their + // templates) and helpers where we want to avoid shadowing camelCase + // expressions + // C) replace `.` with `/` in order to make nested controllers work in the following cases + // 1. `needs: ['posts/post']` + // 2. `{{render "posts/post"}}` + // 3. `this.render('posts/post')` from Route + + let split = fullName.split(':'); + if (split.length > 1) { + let type = split[0]; + + if ( + type === 'component' || + type === 'helper' || + type === 'modifier' || + (type === 'template' && split[1].indexOf('components/') === 0) + ) { + return type + ':' + split[1].replace(/_/g, '-'); + } else { + return type + ':' + dasherize(split[1].replace(/\./g, '/')); + } + } else { + return fullName; + } + } + + pluralize(type) { + return ( + this.pluralizedTypes[type] || (this.pluralizedTypes[type] = type + 's') + ); + } + + podBasedLookupWithPrefix(podPrefix, parsedName) { + let fullNameWithoutType = parsedName.fullNameWithoutType; + + if (parsedName.type === 'template') { + fullNameWithoutType = fullNameWithoutType.replace(/^components\//, ''); + } + + return podPrefix + '/' + fullNameWithoutType + '/' + parsedName.type; + } + + podBasedModuleName(parsedName) { + let podPrefix = + this.namespace.podModulePrefix || this.namespace.modulePrefix; + + return this.podBasedLookupWithPrefix(podPrefix, parsedName); + } + + podBasedComponentsInSubdir(parsedName) { + let podPrefix = + this.namespace.podModulePrefix || this.namespace.modulePrefix; + podPrefix = podPrefix + '/components'; + + if ( + parsedName.type === 'component' || + /^components/.test(parsedName.fullNameWithoutType) + ) { + return this.podBasedLookupWithPrefix(podPrefix, parsedName); + } + } + + resolveEngine(parsedName) { + let engineName = parsedName.fullNameWithoutType; + let engineModule = engineName + '/engine'; + + if (this._moduleRegistry.has(engineModule)) { + return this._extractDefaultExport(engineModule); + } + } + + resolveRouteMap(parsedName) { + let engineName = parsedName.fullNameWithoutType; + let engineRoutesModule = engineName + '/routes'; + + if (this._moduleRegistry.has(engineRoutesModule)) { + let routeMap = this._extractDefaultExport(engineRoutesModule); + + assert( + `The route map for ${engineName} should be wrapped by 'buildRoutes' before exporting.`, + routeMap.isRouteMap + ); + + return routeMap; + } + } + + resolveTemplate(parsedName) { + return this.resolveOther(parsedName); + } + + mainModuleName(parsedName) { + if (parsedName.fullNameWithoutType === 'main') { + // if router:main or adapter:main look for a module with just the type first + return parsedName.prefix + '/' + parsedName.type; + } + } + + defaultModuleName(parsedName) { + return ( + parsedName.prefix + + '/' + + this.pluralize(parsedName.type) + + '/' + + parsedName.fullNameWithoutType + ); + } + + nestedColocationComponentModuleName(parsedName) { + if (parsedName.type === 'component') { + return ( + parsedName.prefix + + '/' + + this.pluralize(parsedName.type) + + '/' + + parsedName.fullNameWithoutType + + '/index' + ); + } + } + + prefix(parsedName) { + let tmpPrefix = this.namespace.modulePrefix; + + if (this.namespace[parsedName.type + 'Prefix']) { + tmpPrefix = this.namespace[parsedName.type + 'Prefix']; + } + + return tmpPrefix; + } + + findModuleName(parsedName, loggingDisabled) { + let moduleNameLookupPatterns = this.moduleNameLookupPatterns; + let moduleName; + + for ( + let index = 0, length = moduleNameLookupPatterns.length; + index < length; + index++ + ) { + let item = moduleNameLookupPatterns[index]; + + let tmpModuleName = item.call(this, parsedName); + + // allow treat all dashed and all underscored as the same thing + // supports components with dashes and other stuff with underscores. + if (tmpModuleName) { + tmpModuleName = this.chooseModuleName(tmpModuleName, parsedName); + } + + if (tmpModuleName && this._moduleRegistry.has(tmpModuleName)) { + moduleName = tmpModuleName; + } + + if (!loggingDisabled) { + this._logLookup(moduleName, parsedName, tmpModuleName); + } + + if (moduleName) { + return moduleName; + } + } + } + + chooseModuleName(moduleName, parsedName) { + let underscoredModuleName = underscore(moduleName); + + if ( + moduleName !== underscoredModuleName && + this._moduleRegistry.has(moduleName) && + this._moduleRegistry.has(underscoredModuleName) + ) { + throw new TypeError( + `Ambiguous module names: '${moduleName}' and '${underscoredModuleName}'` + ); + } + + if (this._moduleRegistry.has(moduleName)) { + return moduleName; + } else if (this._moduleRegistry.has(underscoredModuleName)) { + return underscoredModuleName; + } + // workaround for dasherized partials: + // something/something/-something => something/something/_something + let partializedModuleName = moduleName.replace(/\/-([^/]*)$/, '/_$1'); + + if (this._moduleRegistry.has(partializedModuleName)) { + deprecate( + 'Modules should not contain underscores. ' + + 'Attempted to lookup "' + + moduleName + + '" which ' + + 'was not found. Please rename "' + + partializedModuleName + + '" ' + + 'to "' + + moduleName + + '" instead.', + false, + { + id: 'ember-resolver.underscored-modules', + until: '3.0.0', + for: 'ember-resolver', + since: '0.1.0', + } + ); + + return partializedModuleName; + } + + if (DEBUG) { + let isCamelCaseHelper = + parsedName.type === 'helper' && /[a-z]+[A-Z]+/.test(moduleName); + if (isCamelCaseHelper) { + this._camelCaseHelperWarnedNames = + this._camelCaseHelperWarnedNames || []; + let alreadyWarned = + this._camelCaseHelperWarnedNames.indexOf(parsedName.fullName) > -1; + if (!alreadyWarned && this._moduleRegistry.has(dasherize(moduleName))) { + this._camelCaseHelperWarnedNames.push(parsedName.fullName); + warn( + 'Attempted to lookup "' + + parsedName.fullName + + '" which ' + + 'was not found. In previous versions of ember-resolver, a bug would have ' + + 'caused the module at "' + + dasherize(moduleName) + + '" to be ' + + 'returned for this camel case helper name. This has been fixed. ' + + 'Use the dasherized name to resolve the module that would have been ' + + 'returned in previous versions.', + false, + { id: 'ember-resolver.camelcase-helper-names', until: '3.0.0' } + ); + } + } + } + } + + // used by Ember.DefaultResolver.prototype._logLookup + lookupDescription(fullName) { + let parsedName = this.parseName(fullName); + + let moduleName = this.findModuleName(parsedName, true); + + return moduleName; + } + + // only needed until 1.6.0-beta.2 can be required + _logLookup(found, parsedName, description) { + let owner = getOwner(this); + let env = owner?.resolveRegistration?.('config:environment'); + if (!env?.LOG_MODULE_RESOLVER && !parsedName.root.LOG_RESOLVER) { + return; + } + + let padding; + let symbol = found ? '[✓]' : '[ ]'; + + if (parsedName.fullName.length > 60) { + padding = '.'; + } else { + padding = new Array(60 - parsedName.fullName.length).join('.'); + } + + if (!description) { + description = this.lookupDescription(parsedName); + } + + /* eslint-disable no-console */ + if (console && console.info) { + console.info(symbol, parsedName.fullName, padding, description); + } + } + + knownForType(type) { + let moduleKeys = this._moduleRegistry.moduleNames(); + + let items = Object.create(null); + for (let index = 0, length = moduleKeys.length; index < length; index++) { + let moduleName = moduleKeys[index]; + let fullname = this.translateToContainerFullname(type, moduleName); + + if (fullname) { + items[fullname] = true; + } + } + + return items; + } + + translateToContainerFullname(type, moduleName) { + let prefix = this.prefix({ type }); + + // Note: using string manipulation here rather than regexes for better performance. + // pod modules + // '^' + prefix + '/(.+)/' + type + '$' + let podPrefix = prefix + '/'; + let podSuffix = '/' + type; + let start = moduleName.indexOf(podPrefix); + let end = moduleName.indexOf(podSuffix); + + if ( + start === 0 && + end === moduleName.length - podSuffix.length && + moduleName.length > podPrefix.length + podSuffix.length + ) { + return type + ':' + moduleName.slice(start + podPrefix.length, end); + } + + // non-pod modules + // '^' + prefix + '/' + pluralizedType + '/(.+)$' + let pluralizedType = this.pluralize(type); + let nonPodPrefix = prefix + '/' + pluralizedType + '/'; + + if ( + moduleName.indexOf(nonPodPrefix) === 0 && + moduleName.length > nonPodPrefix.length + ) { + return type + ':' + moduleName.slice(nonPodPrefix.length); + } + } + + _extractDefaultExport(normalizedModuleName) { + let module = this._moduleRegistry.get( + normalizedModuleName, + null, + null, + true /* force sync */ + ); + + if (module && module['default']) { + module = module['default']; + } + + return module; + } +} + +export default Resolver; diff --git a/packages/demo/src/config/router.ts b/packages/demo/src/config/router.ts new file mode 100644 index 00000000000..fc139b8f6cd --- /dev/null +++ b/packages/demo/src/config/router.ts @@ -0,0 +1,104 @@ +import EmberRouter from '@ember/routing/router'; +import config from './env'; +import type Controller from '@ember/controller'; +import Route from '@ember/routing/route'; +import { PrecompiledTemplate } from '@ember/template-compilation'; +import { getOwner } from '@ember/application'; + +/* + Here we use part of lazy-loading logic from https://github.com/embroider-build/embroider/blob/main/packages/router/src/index.ts +*/ + +export type HashReturnType = { + route?: typeof Route | Promise; + controller?: typeof Controller | Promise; + template?: PrecompiledTemplate | Promise; +}; + +class Router extends EmberRouter { + static lazyRoutes: Record HashReturnType> = {}; + location = config.locationType as 'history'; + rootURL = config.rootURL; + loadedRoutes = new Set(); + + // This is necessary in order to prevent the premature loading of lazy routes + // when we are merely trying to render a link-to that points at them. + // Unfortunately the stock query parameter behavior pulls on routes just to + // check what their previous QP values were. + _getQPMeta(handlerInfo: { name: string }, ...rest: unknown[]) { + if ( + handlerInfo.name in Router.lazyRoutes && + !this.loadedRoutes.has(handlerInfo.name) + ) { + return undefined; + } + // @ts-expect-error extending private method + return super._getQPMeta(handlerInfo, ...rest); + } + + // This is the framework method that we're overriding to provide our own + // handlerResolver. + setupRouter(...args: unknown[]) { + // @ts-expect-error extending private method + const isSetup = super.setupRouter(...args); + const microLib = ( + this as unknown as { + // TODO: is there a way don't use the private route? + /* eslint-disable ember/no-private-routing-service */ + _routerMicrolib: { getRoute: (name: string) => unknown }; + } + )._routerMicrolib; + microLib.getRoute = this._handlerResolver(microLib.getRoute.bind(microLib)); + return isSetup; + } + + lazyBundle(name: string) { + if (this.loadedRoutes.has(name)) { + return null; + } + const routeResolver = Router.lazyRoutes[name]; + const owner = getOwner(this); + if (routeResolver) { + return { + load: async () => { + const hash = routeResolver(); + const keys = Object.keys(hash); + const values = await Promise.all(keys.map((key) => hash[key])); + keys.forEach((key, index) => { + // owner.unregister(`${key}:${name}`); + try { + owner.register(`${key}:${name}`, values[index]); + } catch (e) { + // ignore + } + }); + this.loadedRoutes.add(name); + }, + loaded: false, + }; + } + return null; + } + + private _handlerResolver(original: (name: string) => unknown) { + return (name: string) => { + const bundle = this.lazyBundle(name); + + if (!bundle || bundle.loaded) { + return original(name); + } + + return bundle.load().then( + () => { + bundle.loaded = true; + return original(name); + }, + (err: Error) => { + throw err; + } + ); + }; + } +} + +export default Router; diff --git a/packages/demo/src/config/string.ts b/packages/demo/src/config/string.ts new file mode 100644 index 00000000000..f4b591cbea7 --- /dev/null +++ b/packages/demo/src/config/string.ts @@ -0,0 +1,162 @@ +class Cache { + constructor(limit, func, store) { + this.limit = limit; + this.func = func; + this.store = store; + this.size = 0; + this.misses = 0; + this.hits = 0; + this.store = store || new Map(); + } + get(key) { + let value = this.store.get(key); + if (this.store.has(key)) { + this.hits++; + return this.store.get(key); + } else { + this.misses++; + value = this.set(key, this.func(key)); + } + return value; + } + set(key, value) { + if (this.limit > this.size) { + this.size++; + this.store.set(key, value); + } + return value; + } + purge() { + this.store.clear(); + this.size = 0; + this.hits = 0; + this.misses = 0; + } +} +let STRINGS = {}; +export function setStrings(strings) { + STRINGS = strings; +} +export function getStrings() { + return STRINGS; +} +export function getString(name) { + return STRINGS[name]; +} +const STRING_DASHERIZE_REGEXP = /[ _]/g; +const STRING_DASHERIZE_CACHE = new Cache(1000, (key) => + decamelize(key).replace(STRING_DASHERIZE_REGEXP, '-') +); +const STRING_CLASSIFY_REGEXP_1 = /^(\-|_)+(.)?/; +const STRING_CLASSIFY_REGEXP_2 = /(.)(\-|\_|\.|\s)+(.)?/g; +const STRING_CLASSIFY_REGEXP_3 = /(^|\/|\.)([a-z])/g; +const CLASSIFY_CACHE = new Cache(1000, (str) => { + const replace1 = (_match, _separator, chr) => (chr ? `_${chr.toUpperCase()}` : ''); + const replace2 = (_match, initialChar, _separator, chr) => + initialChar + (chr ? chr.toUpperCase() : ''); + const parts = str.split('/'); + for (let i = 0; i < parts.length; i++) { + parts[i] = parts[i] + .replace(STRING_CLASSIFY_REGEXP_1, replace1) + .replace(STRING_CLASSIFY_REGEXP_2, replace2); + } + return parts + .join('/') + .replace(STRING_CLASSIFY_REGEXP_3, (match /*, separator, chr */) => match.toUpperCase()); +}); +const STRING_UNDERSCORE_REGEXP_1 = /([a-z\d])([A-Z]+)/g; +const STRING_UNDERSCORE_REGEXP_2 = /\-|\s+/g; +const UNDERSCORE_CACHE = new Cache(1000, (str) => + str + .replace(STRING_UNDERSCORE_REGEXP_1, '$1_$2') + .replace(STRING_UNDERSCORE_REGEXP_2, '_') + .toLowerCase() +); +const STRING_DECAMELIZE_REGEXP = /([a-z\d])([A-Z])/g; +const DECAMELIZE_CACHE = new Cache(1000, (str) => + str.replace(STRING_DECAMELIZE_REGEXP, '$1_$2').toLowerCase() +); +/** + Converts a camelized string into all lower case separated by underscores. + + ```javascript + import { decamelize } from '@ember/string'; + + decamelize('innerHTML'); // 'inner_html' + decamelize('action_name'); // 'action_name' + decamelize('css-class-name'); // 'css-class-name' + decamelize('my favorite items'); // 'my favorite items' + ``` + + @method decamelize + @param {String} str The string to decamelize. + @return {String} the decamelized string. + @public +*/ +export function decamelize(str) { + return DECAMELIZE_CACHE.get(str); +} +/** + Replaces underscores, spaces, or camelCase with dashes. + + ```javascript + import { dasherize } from '@ember/string'; + + dasherize('innerHTML'); // 'inner-html' + dasherize('action_name'); // 'action-name' + dasherize('css-class-name'); // 'css-class-name' + dasherize('my favorite items'); // 'my-favorite-items' + dasherize('privateDocs/ownerInvoice'; // 'private-docs/owner-invoice' + ``` + + @method dasherize + @param {String} str The string to dasherize. + @return {String} the dasherized string. + @public +*/ +export function dasherize(str) { + return STRING_DASHERIZE_CACHE.get(str); +} +/** + Returns the UpperCamelCase form of a string. + + ```javascript + import { classify } from '@ember/string'; + + classify('innerHTML'); // 'InnerHTML' + classify('action_name'); // 'ActionName' + classify('css-class-name'); // 'CssClassName' + classify('my favorite items'); // 'MyFavoriteItems' + classify('private-docs/owner-invoice'); // 'PrivateDocs/OwnerInvoice' + ``` + + @method classify + @param {String} str the string to classify + @return {String} the classified string + @public +*/ +export function classify(str) { + return CLASSIFY_CACHE.get(str); +} +/** + More general than decamelize. Returns the lower\_case\_and\_underscored + form of a string. + + ```javascript + import { underscore } from '@ember/string'; + + underscore('innerHTML'); // 'inner_html' + underscore('action_name'); // 'action_name' + underscore('css-class-name'); // 'css_class_name' + underscore('my favorite items'); // 'my_favorite_items' + underscore('privateDocs/ownerInvoice'); // 'private_docs/owner_invoice' + ``` + + @method underscore + @param {String} str The string to underscore. + @return {String} the underscored string. + @public +*/ +export function underscore(str) { + return UNDERSCORE_CACHE.get(str); +} diff --git a/packages/demo/src/config/utils.ts b/packages/demo/src/config/utils.ts new file mode 100644 index 00000000000..7a7ba0e7aa9 --- /dev/null +++ b/packages/demo/src/config/utils.ts @@ -0,0 +1,62 @@ +import type Service from '@ember/service'; +import type Controller from '@ember/controller'; +import type Route from '@ember/routing/route'; +import type GlimmerComponent from '@glimmer/component'; +import type Helper from '@ember/component/helper'; +import type Modifier from 'ember-modifier'; +import type { PrecompiledTemplate } from '@ember/template-compilation'; +import { setComponentTemplate } from '@ember/component'; +import env from '@/config/env'; +export type RegisteredComponent = typeof GlimmerComponent & { + template: PrecompiledTemplate; +}; +export type RegistryType = + | 'service' + | 'controller' + | 'route' + | 'template' + | 'component' + | 'helper' + | 'modifier'; +export type RegistryKey = `${RegistryType}:${string}`; +export interface IRegistry { + [key: RegistryKey]: + | typeof Service + | typeof Controller + | typeof Route + | typeof Helper + | Modifier + | RegisteredComponent + | PrecompiledTemplate; +} + +export function registerComponent( + component: T & { template: PrecompiledTemplate } +): RegisteredComponent { + try { + return setComponentTemplate( + component.template, + component as unknown as object + ) as RegisteredComponent; + } catch (e) { + console.error(e); + return component as unknown as RegisteredComponent; + } +} + +export function resoleFromRegistry(key: RegistryKey): T { + // application.__registry__.resolve + return window[env.APP.globalName].resolveRegistration(key) as T; +} + +export function extendRegistry(registry) { + Object.keys(registry).forEach((key) => { + try { + window[env.APP.globalName].register(key, registry[key]); + } catch(e) { + // hot-reload case + window[env.APP.globalName].unregister(key); + window[env.APP.globalName].register(key, registry[key]); + } + }); +} diff --git a/packages/demo/src/controllers/application.ts b/packages/demo/src/controllers/application.ts new file mode 100644 index 00000000000..8c0d478dcd2 --- /dev/null +++ b/packages/demo/src/controllers/application.ts @@ -0,0 +1,9 @@ +import Controller from '@ember/controller'; +import { tracked } from '@glimmer/tracking'; +export class ApplicationController extends Controller { + @tracked showModal = true; + + constructor(...args: ConstructorParameters) { + super(...args); + } +} diff --git a/packages/demo/src/controllers/login.ts b/packages/demo/src/controllers/login.ts new file mode 100644 index 00000000000..d53ff60cac8 --- /dev/null +++ b/packages/demo/src/controllers/login.ts @@ -0,0 +1,15 @@ +import Controller from '@ember/controller'; +import { service } from '@ember/service'; +import { action } from '@ember/object'; +import RouterService from '@ember/routing/router-service'; + +export class LoginController extends Controller { + @service router!: RouterService; + + @action + async authenticate(e: MouseEvent) { + e.preventDefault(); + + await this.session.authenticate('authenticator:custom'); + } +} diff --git a/packages/demo/src/controllers/profile.ts b/packages/demo/src/controllers/profile.ts new file mode 100644 index 00000000000..b8b7620edb9 --- /dev/null +++ b/packages/demo/src/controllers/profile.ts @@ -0,0 +1,22 @@ +import Controller from '@ember/controller'; +import { tracked } from '@glimmer/tracking'; +import type RouterService from '@ember/routing/router-service'; +import { service } from '@ember/service'; + + +export class ProfileController extends Controller { + @tracked now = new Date().toISOString(); + @service router!: RouterService; + + toMain = () => { + this.router.transitionTo('main'); + }; + + constructor(...args: ConstructorParameters) { + super(...args); + // cellFor(this, 'now').update(new Date().toISOString()); + setInterval(() => { + this.now = new Date().toISOString(); + }, 1000); + } +} diff --git a/packages/demo/src/helpers/__mocks__/@ember/component/helper.js b/packages/demo/src/helpers/__mocks__/@ember/component/helper.js new file mode 100644 index 00000000000..e245eb6d266 --- /dev/null +++ b/packages/demo/src/helpers/__mocks__/@ember/component/helper.js @@ -0,0 +1,8 @@ +export default class Helper { + willDestroy() { + // EOL + } + recompute() { + // EOL + } +} diff --git a/packages/demo/src/helpers/is-dev.ts b/packages/demo/src/helpers/is-dev.ts new file mode 100644 index 00000000000..8265b99c49d --- /dev/null +++ b/packages/demo/src/helpers/is-dev.ts @@ -0,0 +1,5 @@ +import env from '@/config/env'; + +export default function isDev(): boolean { + return env.environment === 'development'; +} diff --git a/packages/demo/src/helpers/memory-usage.test.ts b/packages/demo/src/helpers/memory-usage.test.ts new file mode 100644 index 00000000000..f12960745c2 --- /dev/null +++ b/packages/demo/src/helpers/memory-usage.test.ts @@ -0,0 +1,30 @@ +// jest test for memory-usage.ts +import MemoryUsage from './memory-usage'; + +describe('helpers | memory-usage', () => { + it('should retun from computation if no performance property', () => { + const memoryUsage = new MemoryUsage(); + expect(memoryUsage.compute()).toBeNull(); + }); + it('should not call recompute if measureMemory called without performance', async () => { + const memoryUsage = new MemoryUsage(); + const recomputeSpy = jest.spyOn(memoryUsage, 'recompute'); + await memoryUsage.measureMemory(); + expect(recomputeSpy).not.toHaveBeenCalled(); + }); + it('will destroy should clear interval', async () => { + const memoryUsage = new MemoryUsage(); + let a = 1; + const interval = setInterval(() => { + a++; + // NOOP + }, 10); + await new Promise((resolve) => setTimeout(resolve, 100)); + memoryUsage.interval = interval; + expect(memoryUsage.interval).toBe(interval); + const lastIntervalValue = a; + memoryUsage.willDestroy(); + await new Promise((resolve) => setTimeout(resolve, 100)); + expect(a).toBe(lastIntervalValue); + }); +}); diff --git a/packages/demo/src/helpers/memory-usage.ts b/packages/demo/src/helpers/memory-usage.ts new file mode 100644 index 00000000000..f806c72e4b3 --- /dev/null +++ b/packages/demo/src/helpers/memory-usage.ts @@ -0,0 +1,38 @@ +import Helper from '@ember/component/helper'; + +export default class MemoryUsage extends Helper { + interval: number | undefined; + heapSize: number | undefined; + + constructor() { + super(); + + if (!performance.memory) { + return; + } + + this.interval = setInterval(this.measureMemory.bind(this), 1000); + } + + async measureMemory() { + if (!performance.memory) { + return; + } + + const dirtyValue = performance.memory.usedJSHeapSize / 1048576; + + this.heapSize = Math.round(dirtyValue * 100) / 100; + + this.recompute(); + } + + compute() { + return this.heapSize ? `Memory used ${this.heapSize} MB` : null; + } + + willDestroy() { + super.willDestroy(); + + clearInterval(this.interval); + } +} diff --git a/packages/demo/src/initializers/ember-data.ts b/packages/demo/src/initializers/ember-data.ts new file mode 100644 index 00000000000..808a21d1a62 --- /dev/null +++ b/packages/demo/src/initializers/ember-data.ts @@ -0,0 +1,18 @@ +import type Application from '@ember/application'; + +function initializeStore(application: Application) { + application.registerOptionsForType('serializer', { singleton: false }); + application.registerOptionsForType('adapter', { singleton: false }); +} + +function setupContainer(application: Application) { + initializeStore(application); +} + +/* + This code initializes EmberData in an Ember application. +*/ +export default { + name: 'ember-data', + initialize: setupContainer, +}; diff --git a/packages/demo/src/initializers/logger.ts b/packages/demo/src/initializers/logger.ts new file mode 100644 index 00000000000..9427816d9fb --- /dev/null +++ b/packages/demo/src/initializers/logger.ts @@ -0,0 +1,18 @@ +import type Application from '@ember/application'; +import EmberObject from '@ember/object'; + +export class Logger extends EmberObject { + log(m: unknown) { + console.log(m); + } +} + +export function initialize(application: Application) { + application.register('logger:main', Logger); + console.log('logger registered'); +} + +export default { + name: 'logger', + initialize, +}; diff --git a/packages/demo/src/instance-initializers/logger.test.ts b/packages/demo/src/instance-initializers/logger.test.ts new file mode 100644 index 00000000000..c7eeb21e852 --- /dev/null +++ b/packages/demo/src/instance-initializers/logger.test.ts @@ -0,0 +1,18 @@ +import logger from './logger'; + +// jest test for instance-initializers/logger.ts +describe('instance-initializers/logger', () => { + it('should log', () => { + const mockLogger = { + log: jest.fn(), + }; + + logger.initialize({ + lookup() { + return mockLogger; + }, + } as any); + + expect(mockLogger.log).toHaveBeenCalledWith('Instance initializer init'); + }); +}); diff --git a/packages/demo/src/instance-initializers/logger.ts b/packages/demo/src/instance-initializers/logger.ts new file mode 100644 index 00000000000..35889c58555 --- /dev/null +++ b/packages/demo/src/instance-initializers/logger.ts @@ -0,0 +1,13 @@ +import type ApplicationInstance from '@ember/application/instance'; +import type { Logger } from '../initializers/logger'; + +export function initialize(applicationInstance: ApplicationInstance) { + const logger = applicationInstance.lookup('logger:main') as Logger; + + logger.log('Instance initializer init'); +} + +export default { + name: 'logger', + initialize, +}; diff --git a/packages/demo/src/main.ts b/packages/demo/src/main.ts new file mode 100644 index 00000000000..c439d26ea93 --- /dev/null +++ b/packages/demo/src/main.ts @@ -0,0 +1,18 @@ +import './style.css'; + +import Ember from 'ember'; +import App from '@/config/application'; +import { init } from '@/config/initializer'; +import { setupApplicationGlobals } from '@/config/helpers'; +import { extendRegistry } from '@/config/utils'; +import env from '@/config/env'; +import Router from './router'; + +import '@/config/inspector'; + +setupApplicationGlobals(Ember); + +const app = init(App, Router); + +window[env.APP.globalName] = app; // for debugging and experiments +app.visit(window.location.pathname); diff --git a/packages/demo/src/models/person.ts b/packages/demo/src/models/person.ts new file mode 100644 index 00000000000..f1156f17e3f --- /dev/null +++ b/packages/demo/src/models/person.ts @@ -0,0 +1,7 @@ +import Model, { attr, belongsTo, hasMany } from '@ember-data/model'; + +export default class PersonModel extends Model { + @attr name; + @belongsTo('pet', { inverse: 'owner', async: false }) dog; + @hasMany('person', { inverse: 'friends', async: false }) friends; +} diff --git a/packages/demo/src/models/pet.ts b/packages/demo/src/models/pet.ts new file mode 100644 index 00000000000..ce55c58ff2e --- /dev/null +++ b/packages/demo/src/models/pet.ts @@ -0,0 +1,6 @@ +import Model, { attr, belongsTo } from '@ember-data/model'; + +export default class PetModel extends Model { + @attr name; + @belongsTo('person', { inverse: 'dog', async: false }) owner; +} diff --git a/packages/demo/src/modifiers/click-tracker.ts b/packages/demo/src/modifiers/click-tracker.ts new file mode 100644 index 00000000000..ae8e1b37d2f --- /dev/null +++ b/packages/demo/src/modifiers/click-tracker.ts @@ -0,0 +1,26 @@ +import Modifier from 'ember-modifier'; +import { registerDestructor } from '@ember/destroyable'; + +function cleanup(instance: ClickTracker) { + document.body.removeEventListener('click', instance.handler); +} + +export default class ClickTracker extends Modifier { + element!: Element; + + modify(element: Element) { + this.element = element; + + document.body.addEventListener('click', this.handler); + + registerDestructor(this, cleanup); + } + + handler = (e: Event) => { + const place = this.element.contains(e.target as Element) + ? 'inside' + : 'outside'; + + console.log(`Click ${place} ${this.element}`); + }; +} diff --git a/packages/demo/src/router.ts b/packages/demo/src/router.ts new file mode 100644 index 00000000000..de10d70ccb3 --- /dev/null +++ b/packages/demo/src/router.ts @@ -0,0 +1,46 @@ +import Router from '@/config/router'; +// import type { HashReturnType } from '@/config/router'; +// import MainTemplate from './templates/main'; + +export enum Routes { + Main = 'main', + Profile = 'profile', + Login = 'login', + Logout = 'logout', + About = 'about', + NotFound = 'not-found', + Bootstrap = 'bootstrap', +} + +// Router.lazyRoutes = { +// [Routes.Main]: (): HashReturnType => ({ +// // sample of lazy-loaded route, and statically resolved template +// // have no idea how to fix typings here... +// route: import('./routes/main').then((m) => m.MainRoute), +// template: MainTemplate, +// }), +// [Routes.Profile]: (): HashReturnType => ({ +// route: import('./routes/profile').then((m) => m.default), +// template: import('./templates/profile.hbs').then((m) => m.default), +// }), +// [Routes.NotFound]: (): HashReturnType => ({ +// // sample of lazy-loaded route, and dynamically resolved template +// template: import('./templates/not-found').then((m) => m.default), +// }), +// [Routes.Bootstrap]: (): HashReturnType => ({ +// // sample of lazy-loaded route, and dynamically resolved template +// template: import('./templates/bootstrap').then((m) => m.default), +// }), +// }; + +Router.map(function () { + this.route(Routes.Main, { path: '/' }); + this.route(Routes.Profile, { path: '/profile' }); + this.route(Routes.Login, { path: '/login' }); + this.route(Routes.Logout, { path: '/logout' }); + this.route(Routes.About, { path: '/about' }); + this.route(Routes.Bootstrap, { path: '/bootstrap' }); + this.route(Routes.NotFound, { path: '*wildcard_path' }); +}); + +export default Router; diff --git a/packages/demo/src/routes/application.ts b/packages/demo/src/routes/application.ts new file mode 100644 index 00000000000..5e511a5dfb5 --- /dev/null +++ b/packages/demo/src/routes/application.ts @@ -0,0 +1,13 @@ +import Route from '@ember/routing/route'; + +export class ApplicationRoute extends Route { + + async beforeModel() { + console.log('before model'); + } + + model() { + console.log('model'); + return ['red', 'yellow', 'blue']; + } +} diff --git a/packages/demo/src/routes/login.ts b/packages/demo/src/routes/login.ts new file mode 100644 index 00000000000..37a3ee3a977 --- /dev/null +++ b/packages/demo/src/routes/login.ts @@ -0,0 +1,5 @@ +import Route from '@ember/routing/route'; + +export default class LoginRoute extends Route { + +} diff --git a/packages/demo/src/routes/logout.ts b/packages/demo/src/routes/logout.ts new file mode 100644 index 00000000000..81960ad7c39 --- /dev/null +++ b/packages/demo/src/routes/logout.ts @@ -0,0 +1,13 @@ +import Route from '@ember/routing/route'; +import RouterService from '@ember/routing/router-service'; +import Transition from '@ember/routing/transition'; +import { service } from '@ember/service'; + +export default class LogoutRoute extends Route { + @service router!: RouterService; + + async beforeModel(transition: Transition) { + + this.router.transitionTo('main'); + } +} diff --git a/packages/demo/src/routes/main.ts b/packages/demo/src/routes/main.ts new file mode 100644 index 00000000000..1a62e7ca7c4 --- /dev/null +++ b/packages/demo/src/routes/main.ts @@ -0,0 +1,7 @@ +import Route from '@ember/routing/route'; + +export class MainRoute extends Route { + model() { + return ['foo', 'boo', 'blue']; + } +} diff --git a/packages/demo/src/routes/profile.ts b/packages/demo/src/routes/profile.ts new file mode 100644 index 00000000000..05a295faf56 --- /dev/null +++ b/packages/demo/src/routes/profile.ts @@ -0,0 +1,18 @@ +import Route from '@ember/routing/route'; +import type Transition from '@ember/routing/transition'; +export default class ProfileRoute extends Route { + queryParams: Record = { + q: { + refreshModel: true, + replace: true, + } + } + + beforeModel(transition: Transition) { + console.log('beforeModel:profile', transition); + } + + model() { + return [1, 2, 3]; + } +} diff --git a/packages/demo/src/services/date.ts b/packages/demo/src/services/date.ts new file mode 100644 index 00000000000..57e6e05992c --- /dev/null +++ b/packages/demo/src/services/date.ts @@ -0,0 +1,29 @@ +import Service from '@ember/service'; +import { tracked } from '@glimmer/tracking'; +import { cached } from 'tracked-toolbox'; + +export default class DateService extends Service { + interval: ReturnType<(typeof window)['setInterval']> | null = null; + @tracked _date = new Date(); + + constructor(...args: ConstructorParameters) { + super(...args); + + this.interval = setInterval(() => { + this._date = new Date(); + }, 1000); + } + + willDestroy(...args: Parameters) { + super.willDestroy(...args); + if (this.interval) { + clearInterval(this.interval); + } + } + + @cached + get date() { + console.log('ama cached getter, recalculating only on value change') + return this._date.toLocaleTimeString(); + } +} diff --git a/packages/demo/src/services/store.ts b/packages/demo/src/services/store.ts new file mode 100644 index 00000000000..8ed8aa05539 --- /dev/null +++ b/packages/demo/src/services/store.ts @@ -0,0 +1,23 @@ +import Cache from '@ember-data/json-api'; +import EDataStore, { CacheHandler } from '@ember-data/store'; +import RequestManager from '@ember-data/request'; +import Fetch from '@ember-data/request/fetch'; +import Adapter from '@ember-data/adapter/json-api'; + +export default class Store extends EDataStore { + #adapter = new Adapter(); + + adapterFor() { + return this.#adapter; + } + constructor() { + // eslint-disable-next-line prefer-rest-params + super(...arguments); + this.requestManager = new RequestManager(); + this.requestManager.use([Fetch]); + this.requestManager.useCache(CacheHandler); + } + createCache(storeWrapper) { + return new Cache(storeWrapper); + } +} diff --git a/packages/demo/src/style.css b/packages/demo/src/style.css new file mode 100644 index 00000000000..d8693c21ccb --- /dev/null +++ b/packages/demo/src/style.css @@ -0,0 +1,12 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +a.active { + text-decoration: underline; + text-decoration-color: rgb(218, 118, 118); +} + +code { + color: green; +} \ No newline at end of file diff --git a/packages/demo/src/templates/about.hbs b/packages/demo/src/templates/about.hbs new file mode 100644 index 00000000000..ac4dc94debe --- /dev/null +++ b/packages/demo/src/templates/about.hbs @@ -0,0 +1,14 @@ +{{page-title "Page Title: About"}} +

About

+ +
+
+

Need more bandwidth?

+
+

Ember Application, powered by Vite

+
+
+ Home +
+
+
diff --git a/packages/demo/src/templates/application.hbs b/packages/demo/src/templates/application.hbs new file mode 100644 index 00000000000..2fa0dd57f09 --- /dev/null +++ b/packages/demo/src/templates/application.hbs @@ -0,0 +1,60 @@ +
+ +{{#if this.showModal}} + +
+ Ember Modal Dialog test +
+
+{{/if}} + +
+
+ + {{#if (media 'isDesktop')}} + looks like you on Desktop + {{else if (media 'isTablet')}} + looks like you on Tablet + {{else if (media 'isMobile')}} + looks like you on Mobile + {{else}} + wow + {{/if}} + + {{outlet}} + +
+
+
+ +
+
+

Here is your login status

+

+ {{#if this.session.isAuthenticated}} + You are authenticated as + {{this.session.data.authenticated.name}} + {{else}} + You are not authenticated + {{/if}} +

+
+
+
+ +
+
+ +