Skip to content

Commit

Permalink
feat(checkpoints): add useCheckpoint composable
Browse files Browse the repository at this point in the history
  • Loading branch information
nickmessing committed Jul 26, 2024
1 parent 3b2de42 commit ec0ce29
Show file tree
Hide file tree
Showing 38 changed files with 561 additions and 36 deletions.
12 changes: 11 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,15 @@
"vue.complete.casing.tags": "pascal",
"typescript.preferences.importModuleSpecifier": "non-relative",
"typescript.tsdk": "node_modules/typescript/lib",
"cSpell.words": ["composables", "Pinia", "Tinybase", "todos", "wildcarded"]
"cSpell.words": [
"composables",
"Moraru",
"Pinia",
"pkgroll",
"Tinybase",
"todos",
"vitepress",
"vueuse",
"wildcarded"
]
}
7 changes: 7 additions & 0 deletions packages/private/docs/.vitepress/config.mts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,13 @@ export default defineConfig({
{ text: 'Writable References', link: '/api/store/references' },
],
},
{
text: 'Checkpoints',
items: [
{ text: 'Composables', link: '/api/checkpoints/composables' },
{ text: 'Context', link: '/api/checkpoints/context' },
],
},
{
text: 'Common',
items: [
Expand Down
10 changes: 9 additions & 1 deletion packages/private/docs/.vitepress/theme/components/MyLayout.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,27 @@ import { useRoute } from 'vitepress'
import DefaultTheme from 'vitepress/theme'
import VPSwitchStoreStyle from './VPSwitchStoreStyle.vue'
import { computed } from 'vue'
import { useBodyClass } from '../composables/useBodyClass.mjs'
import { useLocalStorage } from '@vueuse/core'
const { Layout } = DefaultTheme
const route = useRoute()
const isApiPage = computed(() => route.path.startsWith('/api'))
const isDefaultStore = useLocalStorage('isDefaultStoreSelected', true)
useBodyClass(isDefaultStore, {
trueClass: 'default-store',
falseClass: 'custom-store',
})
</script>

<template>
<Layout>
<template #sidebar-nav-before>
<ClientOnly v-if="isApiPage">
<VPSwitchStoreStyle />
<VPSwitchStoreStyle v-model="isDefaultStore" />
</ClientOnly>
</template>
</Layout>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,25 +1,12 @@
<script lang="ts" setup>
// copy/pasted from https://github.com/vuejs/vitepress/blob/8fef47848bd3418014b06eea1337b1e66e0473c6/src/client/theme-default/components/VPSwitchAppearance.vue
// minor modifications
import { watch, ref } from 'vue'
import VPSwitch from './VPSwitch.vue'
const isDefaultStoreSelected = ref(localStorage.getItem('isDefaultStoreSelected') !== 'false')
watch(
isDefaultStoreSelected,
() => {
localStorage.setItem('isDefaultStoreSelected', isDefaultStoreSelected.value.toString())
if (isDefaultStoreSelected.value) {
document.body.classList.add('default-store')
document.body.classList.remove('custom-store')
} else {
document.body.classList.remove('default-store')
document.body.classList.add('custom-store')
}
},
{ immediate: true },
)
const isDefaultStoreSelected = defineModel({
type: Boolean,
required: true,
})
</script>

<template>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { MaybeRefOrGetter, toRef, watch } from 'vue'

interface UseBodyClassOptions {
trueClass: string
falseClass: string
}

export function useBodyClass(value: MaybeRefOrGetter<boolean>, options: UseBodyClassOptions): void {
const { trueClass } = options
const { falseClass } = options

const updateBodyClass = (newValue: boolean) => {
document.body.classList.remove(newValue ? falseClass : trueClass)
document.body.classList.add(newValue ? trueClass : falseClass)
}

watch(toRef(value), updateBodyClass, { immediate: true })
}
76 changes: 76 additions & 0 deletions packages/private/docs/api/checkpoints/composables.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# Composables {#composables}

Checkpoinnts composables.

## useCheckpoint {#use-checkpoint}

The `useCheckpoint` composable returns the label for a checkpoint, and registers a listener so that any changes to that result will cause a re-render.

When first accessed, this composable will create a listener so that changes to the label will cause a re-render. When the component containing this composable is unmounted, the listener will be automatically removed.

### Parameters

<div class="hide-default-store">

- `checkpoints` ([`Checkpoints`](https://tinybase.org/api/checkpoints/interfaces/checkpoints/checkpoints/)): The [`Checkpoints`](https://tinybase.org/api/checkpoints/interfaces/checkpoints/checkpoints/) object to be accessed.

</div>

- `checkpointId` ([`MaybeRefOrGetter`](https://vuejs.org/api/utility-types.html#maybereforgetter)`<string>`): The [Id](https://tinybase.org/api/common/type-aliases/identity/id/) of the checkpoint.

### Returns

- `ComputedRef<string | undefined>`: A **readonly** reference to the string label for the requested checkpoint, an empty string if it was never set, or `undefined` if the checkpoint does not exist..

### Example

<div class="hide-default-store">

```vue
<script setup lang="ts">
import { useCell, injectStore, useCheckpoint } from 'vue-tinybase/custom-store'
import { Store1Key, Checkpoints1Key } from './store'
const store = injectStore(Store1Key)
const checkpoints = injectCheckpoints(Checkpoints1Key)
const checkpointLabel = useCheckpoint(checkpoints, '1')
// UI will be empty
store.setCell('pets', 'fido', 'sold', true)
checkpoints.addCheckpoint('sale')
// UI will show: 'sale'
</script>
<template>
<div>{{ checkpointLabel }}</div>
</template>
```

</div>

<div class="hide-custom-store">

```vue
<script setup lang="ts">
import { useCell, injectStore, useCheckpoint, injectCheckpoints } from 'vue-tinybase'
const store = injectStore()
const checkpoints = injectCheckpoints()
const checkpointLabel = useCheckpoint('1')
// UI will be empty
store.setCell('pets', 'fido', 'sold', true)
checkpoints.addCheckpoint('sale')
// UI will show: 'sale'
</script>
<template>
<div>{{ checkpointLabel }}</div>
</template>
```

</div>
122 changes: 122 additions & 0 deletions packages/private/docs/api/checkpoints/context.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
# Context {#context}

Context-related functions for providing and injecting checkpoints object into the app or part of the app.

For all the examples assume the following `store.ts` file:

<div class="hide-custom-store">

```ts
import { createStore, createCheckpoints } from 'tinybase'

export const store = createStore()
export const checkpoints = createCheckpoints(store)
```

</div>

<div class="hide-default-store">

```ts
import { createStore, createCheckpoints } from 'tinybase'
import type { InjectionKey } from 'vue'

export const store1 = createStore()
export const store2 = createStore()

export const checkpoints1 = createCheckpoints(store1)
export const checkpoints2 = createCheckpoints(store2)

export const Store1Key = Symbol('Store1') as InjectionKey<typeof store1>
export const Store2Key = Symbol('Store2') as InjectionKey<typeof store2>

export const Checkpoints1Key = Symbol('Checkpoints1') as InjectionKey<typeof checkpoints1>
export const Checkpoints2Key = Symbol('Checkpoints2') as InjectionKey<typeof checkpoints2>
```

</div>

## provideCheckpoints {#provide-checkpoints}

Provide a checkpoints object to all child components, enabling them to access the checkpoints object without having to pass it down as a prop.

- **Parameters**

<div class="hide-default-store">

- `checkpointsKey` (`string | symbol`): Unique injection key.

</div>

- `checkpoints` ([`Checkpoints`](https://tinybase.org/api/checkpoints/interfaces/checkpoints/checkpoints/)): The checkpoints object to provide.

- **Example**

<div class="hide-custom-store">

```vue
<script setup lang="ts">
import { provideCheckpoints } from 'vue-tinybase'
import { checkpoints } from './store'
provideCheckpoints(checkpoints)
</script>
```

</div>

<div class="hide-default-store">

```vue
<script setup lang="ts">
import { provideCheckpoints } from 'vue-tinybase/custom-store'
import { checkpoints1, checkpoints2, Checkpoints1Key, Checkpoints2Key } from './store'
provideCheckpoints(Checkpoints1Key, checkpoints1)
provideCheckpoints(Checkpoints2Key, checkpoints2)
</script>
```

</div>

## injectCheckpoints {#inject-checkpoints}

Inject a checkpoints object provided by [`provideCheckpoints`](/api/checkpoints/context#provide-checkpoints).

<div class="hide-default-store">

- **Parameters**

- `checkpointsKey` (`string | symbol`): Checkpoints object injection key.

</div>

- **Example**

<div class="hide-custom-store">

```vue
<script setup lang="ts">
import { injectCheckpoints } from 'vue-tinybase'
const store = injectCheckpoints()
</script>
```

</div>

<div class="hide-default-store">

```vue
<script setup lang="ts">
import { injectCheckpoints } from 'vue-tinybase/custom-store'
import { Checkpoints1Key } from './store'
const checkpoints1 = injectCheckpoints(Checkpoints1Key)
</script>
```

</div>
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,5 @@ app.mount('#app')
In this example, we create a Vue app and use the provideStore function in the setup function to provide the TinyBase store. The render function is used to render the main App component.

After providing the store, you can use `vue-tinybase` [event hooks](/api/store/events), [composables](/api/store/composables), and [writable references](/api/store/references) inside all components of your app. This integration enables you to interact with the store’s state and respond to changes efficiently.

There are similar provide-related functions for [checkpoints](/api/checkpoints/context) that you can use to provide these objects to your app or part of the app.
7 changes: 6 additions & 1 deletion packages/private/docs/guide/usage-with-typescript.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ For more information on Schema-based typing, please refer to the [official TinyB
The following code snippet demonstrates how to define a schema for your store and connect it to all composables imported from `vue-tinybase`:

```typescript
import { createStore } from 'tinybase/with-schemas'
import { createStore, createCheckpoints } from 'tinybase/with-schemas'

// Create a store with schema-based typing
export const store = createStore()
Expand All @@ -27,13 +27,18 @@ export const store = createStore()
val3: { type: 'boolean', default: false },
})

// If you want - also create checkpoints
const checkpoints = createCheckpoints(store)

// Export the store type
export type Store = typeof store
export type Checkpoints = typeof checkpoints

// Extend the Vue-Tinybase context with the store type
declare module 'vue-tinybase' {
export interface VueTinybaseContext {
store: Store
checkpoints: Checkpoints
}
}
```
1 change: 1 addition & 0 deletions packages/private/docs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"vitepress": "^1.2.3"
},
"dependencies": {
"@vueuse/core": "^10.11.0",
"vue": "^3.4.31"
}
}
9 changes: 7 additions & 2 deletions packages/public/vue-tinybase/src/@types/_internal/common.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
import type { Ref } from '@vue/reactivity'
import type { Store as StoreWithoutSchemas, Id, IdOrNull } from 'tinybase'
import type { OptionalSchemas, Store as StoreWithSchemas } from 'tinybase/with-schemas'
import type { Store as StoreWithoutSchemas, Id, IdOrNull, Checkpoints as CheckpointsWithoutSchemas } from 'tinybase'
import type {
OptionalSchemas,
Store as StoreWithSchemas,
Checkpoints as CheckpointsWithSchemas,
} from 'tinybase/with-schemas'

export type AnyStore = StoreWithoutSchemas | StoreWithSchemas<any>
export type AnyCheckpoints = CheckpointsWithoutSchemas | CheckpointsWithSchemas<any>

export type ExtractSchemasFromStore<Store extends AnyStore> =
Store extends StoreWithSchemas<infer Schema> ? Schema : OptionalSchemas
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import type { UseCheckpointFunction as UseCheckpointFunctionWithSchemas } from './with-schemas/composables.js'
import type { UseCheckpointFunction as UseCheckpointFunctionWithoutSchemas } from './without-schemas/composables.js'

export type UseCheckpointFunction = UseCheckpointFunctionWithSchemas & UseCheckpointFunctionWithoutSchemas

export type { UseCheckpointFunction as UseCheckpointFunctionWithSchemas } from './with-schemas/composables.js'
export type { UseCheckpointFunction as UseCheckpointFunctionWithoutSchemas } from './without-schemas/composables.js'
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './composables.js'
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import type { ComputedRef, MaybeRefOrGetter } from '@vue/reactivity'
import type { Checkpoints, Id } from 'tinybase/with-schemas'

export type UseCheckpointFunction = (
checkpoints: Checkpoints<any>,
checkpointId: MaybeRefOrGetter<Id>,
) => ComputedRef<string | undefined>
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import type { ComputedRef, MaybeRefOrGetter } from '@vue/reactivity'
import type { Checkpoints, Id } from 'tinybase'

export type UseCheckpointFunction = (
checkpoints: Checkpoints,
checkpointId: MaybeRefOrGetter<Id>,
) => ComputedRef<string | undefined>
2 changes: 2 additions & 0 deletions packages/public/vue-tinybase/src/@types/custom-store/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
export * from './composables.js'
export * from './events.js'
export * from './references.js'
export * from './checkpoints/index.js'
Loading

0 comments on commit ec0ce29

Please sign in to comment.