From e5021187e96b78b53203bd95d08d6818aea47d17 Mon Sep 17 00:00:00 2001 From: "Thomas F. K. Jorna" Date: Wed, 14 Jul 2021 15:10:31 +0200 Subject: New Ignite 7.0.6 app --- app/models/character-store/character-store.test.ts | 7 +++ app/models/character-store/character-store.ts | 37 +++++++++++++++ app/models/character/character.test.ts | 10 ++++ app/models/character/character.ts | 17 +++++++ app/models/environment.ts | 40 ++++++++++++++++ app/models/extensions/with-environment.ts | 17 +++++++ app/models/extensions/with-root-store.ts | 17 +++++++ app/models/index.ts | 5 ++ app/models/root-store/root-store-context.ts | 22 +++++++++ app/models/root-store/root-store.ts | 20 ++++++++ app/models/root-store/setup-root-store.ts | 55 ++++++++++++++++++++++ 11 files changed, 247 insertions(+) create mode 100644 app/models/character-store/character-store.test.ts create mode 100644 app/models/character-store/character-store.ts create mode 100644 app/models/character/character.test.ts create mode 100644 app/models/character/character.ts create mode 100644 app/models/environment.ts create mode 100644 app/models/extensions/with-environment.ts create mode 100644 app/models/extensions/with-root-store.ts create mode 100644 app/models/index.ts create mode 100644 app/models/root-store/root-store-context.ts create mode 100644 app/models/root-store/root-store.ts create mode 100644 app/models/root-store/setup-root-store.ts (limited to 'app/models') diff --git a/app/models/character-store/character-store.test.ts b/app/models/character-store/character-store.test.ts new file mode 100644 index 0000000..6d82079 --- /dev/null +++ b/app/models/character-store/character-store.test.ts @@ -0,0 +1,7 @@ +import { CharacterStoreModel } from "./character-store" + +test("can be created", () => { + const instance = CharacterStoreModel.create({}) + + expect(instance).toBeTruthy() +}) diff --git a/app/models/character-store/character-store.ts b/app/models/character-store/character-store.ts new file mode 100644 index 0000000..bd92615 --- /dev/null +++ b/app/models/character-store/character-store.ts @@ -0,0 +1,37 @@ +import { Instance, SnapshotOut, types } from "mobx-state-tree" +import { CharacterModel, CharacterSnapshot } from "../character/character" +import { CharacterApi } from "../../services/api/character-api" +import { withEnvironment } from "../extensions/with-environment" + +/** + * Example store containing Rick and Morty characters + */ +export const CharacterStoreModel = types + .model("CharacterStore") + .props({ + characters: types.optional(types.array(CharacterModel), []), + }) + .extend(withEnvironment) + .actions((self) => ({ + saveCharacters: (characterSnapshots: CharacterSnapshot[]) => { + self.characters.replace(characterSnapshots) + }, + })) + .actions((self) => ({ + getCharacters: async () => { + const characterApi = new CharacterApi(self.environment.api) + const result = await characterApi.getCharacters() + + if (result.kind === "ok") { + self.saveCharacters(result.characters) + } else { + __DEV__ && console.tron.log(result.kind) + } + }, + })) + +type CharacterStoreType = Instance +export interface CharacterStore extends CharacterStoreType {} +type CharacterStoreSnapshotType = SnapshotOut +export interface CharacterStoreSnapshot extends CharacterStoreSnapshotType {} +export const createCharacterStoreDefaultModel = () => types.optional(CharacterStoreModel, {}) diff --git a/app/models/character/character.test.ts b/app/models/character/character.test.ts new file mode 100644 index 0000000..2f330b4 --- /dev/null +++ b/app/models/character/character.test.ts @@ -0,0 +1,10 @@ +import { CharacterModel } from "./character" + +test("can be created", () => { + const instance = CharacterModel.create({ + id: 1, + name: "Rick Sanchez", + }) + + expect(instance).toBeTruthy() +}) diff --git a/app/models/character/character.ts b/app/models/character/character.ts new file mode 100644 index 0000000..ca3cbc7 --- /dev/null +++ b/app/models/character/character.ts @@ -0,0 +1,17 @@ +import { Instance, SnapshotOut, types } from "mobx-state-tree" + +/** + * Rick and Morty character model. + */ +export const CharacterModel = types.model("Character").props({ + id: types.identifierNumber, + name: types.maybe(types.string), + status: types.maybe(types.string), + image: types.maybe(types.string), +}) + +type CharacterType = Instance +export interface Character extends CharacterType {} +type CharacterSnapshotType = SnapshotOut +export interface CharacterSnapshot extends CharacterSnapshotType {} +export const createCharacterDefaultModel = () => types.optional(CharacterModel, {}) diff --git a/app/models/environment.ts b/app/models/environment.ts new file mode 100644 index 0000000..e8569b1 --- /dev/null +++ b/app/models/environment.ts @@ -0,0 +1,40 @@ +import { Api } from "../services/api" + +let ReactotronDev +if (__DEV__) { + const { Reactotron } = require("../services/reactotron") + ReactotronDev = Reactotron +} + +/** + * The environment is a place where services and shared dependencies between + * models live. They are made available to every model via dependency injection. + */ +export class Environment { + constructor() { + // create each service + if (__DEV__) { + // dev-only services + this.reactotron = new ReactotronDev() + } + this.api = new Api() + } + + async setup() { + // allow each service to setup + if (__DEV__) { + await this.reactotron.setup() + } + await this.api.setup() + } + + /** + * Reactotron is only available in dev. + */ + reactotron: typeof ReactotronDev + + /** + * Our api. + */ + api: Api +} diff --git a/app/models/extensions/with-environment.ts b/app/models/extensions/with-environment.ts new file mode 100644 index 0000000..9fe5fd5 --- /dev/null +++ b/app/models/extensions/with-environment.ts @@ -0,0 +1,17 @@ +import { getEnv, IStateTreeNode } from "mobx-state-tree" +import { Environment } from "../environment" + +/** + * Adds a environment property to the node for accessing our + * Environment in strongly typed. + */ +export const withEnvironment = (self: IStateTreeNode) => ({ + views: { + /** + * The environment. + */ + get environment() { + return getEnv(self) + }, + }, +}) diff --git a/app/models/extensions/with-root-store.ts b/app/models/extensions/with-root-store.ts new file mode 100644 index 0000000..eff769c --- /dev/null +++ b/app/models/extensions/with-root-store.ts @@ -0,0 +1,17 @@ +import { getRoot, IStateTreeNode } from "mobx-state-tree" +import { RootStoreModel } from "../root-store/root-store" + +/** + * Adds a rootStore property to the node for a convenient + * and strongly typed way for stores to access other stores. + */ +export const withRootStore = (self: IStateTreeNode) => ({ + views: { + /** + * The root store. + */ + get rootStore() { + return getRoot(self) + }, + }, +}) diff --git a/app/models/index.ts b/app/models/index.ts new file mode 100644 index 0000000..3538dbb --- /dev/null +++ b/app/models/index.ts @@ -0,0 +1,5 @@ +export * from "./extensions/with-environment" +export * from "./extensions/with-root-store" +export * from "./root-store/root-store" +export * from "./root-store/root-store-context" +export * from "./root-store/setup-root-store" diff --git a/app/models/root-store/root-store-context.ts b/app/models/root-store/root-store-context.ts new file mode 100644 index 0000000..537e51c --- /dev/null +++ b/app/models/root-store/root-store-context.ts @@ -0,0 +1,22 @@ +import { createContext, useContext } from "react" +import { RootStore } from "./root-store" + +/** + * Create a context we can use to + * - Provide access to our stores from our root component + * - Consume stores in our screens (or other components, though it's + * preferable to just connect screens) + */ +const RootStoreContext = createContext({} as RootStore) + +/** + * The provider our root component will use to expose the root store + */ +export const RootStoreProvider = RootStoreContext.Provider + +/** + * A hook that screens can use to gain access to our stores, with + * `const { someStore, someOtherStore } = useStores()`, + * or less likely: `const rootStore = useStores()` + */ +export const useStores = () => useContext(RootStoreContext) diff --git a/app/models/root-store/root-store.ts b/app/models/root-store/root-store.ts new file mode 100644 index 0000000..1131b48 --- /dev/null +++ b/app/models/root-store/root-store.ts @@ -0,0 +1,20 @@ +import { Instance, SnapshotOut, types } from "mobx-state-tree" +import { CharacterStoreModel } from "../character-store/character-store" + +/** + * A RootStore model. + */ +// prettier-ignore +export const RootStoreModel = types.model("RootStore").props({ + characterStore: types.optional(CharacterStoreModel, {} as any), +}) + +/** + * The RootStore instance. + */ +export interface RootStore extends Instance {} + +/** + * The data of a RootStore. + */ +export interface RootStoreSnapshot extends SnapshotOut {} diff --git a/app/models/root-store/setup-root-store.ts b/app/models/root-store/setup-root-store.ts new file mode 100644 index 0000000..4a6d0c5 --- /dev/null +++ b/app/models/root-store/setup-root-store.ts @@ -0,0 +1,55 @@ +import { onSnapshot } from "mobx-state-tree" +import { RootStoreModel, RootStore } from "./root-store" +import { Environment } from "../environment" +import * as storage from "../../utils/storage" + +/** + * The key we'll be saving our state as within async storage. + */ +const ROOT_STATE_STORAGE_KEY = "root" + +/** + * Setup the environment that all the models will be sharing. + * + * The environment includes other functions that will be picked from some + * of the models that get created later. This is how we loosly couple things + * like events between models. + */ +export async function createEnvironment() { + const env = new Environment() + await env.setup() + return env +} + +/** + * Setup the root state. + */ +export async function setupRootStore() { + let rootStore: RootStore + let data: any + + // prepare the environment that will be associated with the RootStore. + const env = await createEnvironment() + try { + // load data from storage + data = (await storage.load(ROOT_STATE_STORAGE_KEY)) || {} + rootStore = RootStoreModel.create(data, env) + } catch (e) { + // if there's any problems loading, then let's at least fallback to an empty state + // instead of crashing. + rootStore = RootStoreModel.create({}, env) + + // but please inform us what happened + __DEV__ && console.tron.error(e.message, null) + } + + // reactotron logging + if (__DEV__) { + env.reactotron.setRootStore(rootStore, data) + } + + // track changes & save to storage + onSnapshot(rootStore, (snapshot) => storage.save(ROOT_STATE_STORAGE_KEY, snapshot)) + + return rootStore +} -- cgit v1.2.3