} from './formatting'
import { isPinia, toastMessage } from './utils'
-/**
- * Registered stores used for devtools.
- */
-const registeredStores = /*#__PURE__*/ new Map<string, Store>()
-
let isAlreadyInstalled: boolean | undefined
// timeline can be paused when directly changing the state
let isTimelineActive = true
const MUTATIONS_LAYER_ID = 'pinia:mutations'
const INSPECTOR_ID = 'pinia'
-function addDevtools(app: App, store: Store) {
- // TODO: we probably need to ensure the latest version of the store is kept:
- // without effectScope, multiple stores will be created and will have a
- // limited lifespan for getters.
- // add a dev only variable that is removed in unmounted and replace the store
- let hasSubscribed = true
- const storeType = '🍍 ' + store.$id
- if (!registeredStores.has(store.$id)) {
- registeredStores.set(store.$id, store)
- componentStateTypes.push(storeType)
- hasSubscribed = false
+/**
+ * Gets the displayed name of a store in devtools
+ *
+ * @param id - id of the store
+ * @returns a formatted string
+ */
+const getStoreType = (id: string) => '🍍 ' + id
+
+function addDevtools(app: App, pinia: Pinia, store: Store) {
+ if (!componentStateTypes.includes(getStoreType(store.$id))) {
+ componentStateTypes.push(getStoreType(store.$id))
}
setupDevtoolsPlugin(
{
icon: 'content_copy',
action: () => {
- actionGlobalCopyState(store._p)
+ actionGlobalCopyState(pinia)
},
tooltip: 'Serialize and copy the state',
},
{
icon: 'content_paste',
action: async () => {
- await actionGlobalPasteState(store._p)
+ await actionGlobalPasteState(pinia)
api.sendInspectorTree(INSPECTOR_ID)
api.sendInspectorState(INSPECTOR_ID)
},
{
icon: 'save',
action: () => {
- actionGlobalSaveState(store._p)
+ actionGlobalSaveState(pinia)
},
tooltip: 'Save the state as a JSON file',
},
{
icon: 'folder_open',
action: async () => {
- await actionGlobalOpenStateFile(store._p)
+ await actionGlobalOpenStateFile(pinia)
api.sendInspectorTree(INSPECTOR_ID)
api.sendInspectorState(INSPECTOR_ID)
},
Object.values(piniaStores).forEach((store) => {
payload.instanceData.state.push({
- type: storeType,
+ type: getStoreType(store.$id),
key: 'state',
editable: true,
value: store.$state,
if (store._getters && store._getters.length) {
payload.instanceData.state.push({
- type: storeType,
+ type: getStoreType(store.$id),
key: 'getters',
editable: false,
value: store._getters.reduce((getters, key) => {
api.on.getInspectorTree((payload) => {
if (payload.app === app && payload.inspectorId === INSPECTOR_ID) {
- let stores: Array<Store | Pinia> = [store._p]
- stores = stores.concat(Array.from(registeredStores.values()))
+ let stores: Array<Store | Pinia> = [pinia]
+ stores = stores.concat(Array.from(pinia._s.values()))
payload.rootNodes = (
payload.filter
if (payload.app === app && payload.inspectorId === INSPECTOR_ID) {
const inspectedStore =
payload.nodeId === PINIA_ROOT_ID
- ? store._p
- : registeredStores.get(payload.nodeId)
+ ? pinia
+ : pinia._s.get(payload.nodeId)
if (!inspectedStore) {
// this could be the selected store restored for a different project
if (payload.app === app && payload.inspectorId === INSPECTOR_ID) {
const inspectedStore =
payload.nodeId === PINIA_ROOT_ID
- ? store._p
- : registeredStores.get(payload.nodeId)
+ ? pinia
+ : pinia._s.get(payload.nodeId)
if (!inspectedStore) {
return toastMessage(
const { path } = payload
- if (!isPinia(store)) {
+ if (!isPinia(inspectedStore)) {
// access only the state
if (
path.length !== 1 ||
- !store._customProperties.has(path[0]) ||
- path[0] in store.$state
+ !inspectedStore._customProperties.has(path[0]) ||
+ path[0] in inspectedStore.$state
) {
path.unshift('$state')
}
api.on.editComponentState((payload) => {
if (payload.type.startsWith('🍍')) {
const storeId = payload.type.replace(/^🍍\s*/, '')
- const store = registeredStores.get(storeId)
+ const store = pinia._s.get(storeId)
if (!store) {
return toastMessage(`store "${storeId}" not found`, 'error')
api.sendInspectorState(INSPECTOR_ID)
}
- // avoid subscribing to mutations and actions twice
- if (hasSubscribed) return
-
- store.$onAction(({ after, onError, name, args, store }) => {
+ store.$onAction(({ after, onError, name, args }) => {
const groupId = runningActionId++
api.addTimelineEvent({
},
})
})
- })
+ }, true)
store.$subscribe(({ events, type }, state) => {
if (!isTimelineActive) return
layerId: MUTATIONS_LAYER_ID,
event: eventData,
})
- })
+ }, true)
// trigger an update so it can display new registered stores
- // @ts-ignore
api.notifyComponentUpdate()
toastMessage(`"${store.$id}" store installed`)
}
S extends StateTree = StateTree,
G extends GettersTree<S> = GettersTree<S>,
A /* extends ActionsTree */ = ActionsTree
->({ app, store, options }: PiniaPluginContext<Id, S, G, A>) {
+>({ app, store, options, pinia }: PiniaPluginContext<Id, S, G, A>) {
// HMR module
if (store.$id.startsWith('__hot:')) {
return
addDevtools(
app,
+ pinia,
// @ts-expect-error: FIXME: if possible...
store
)
* Setups a callback to be called whenever the state changes. It also returns
* a function to remove the callback. Note than when calling
* `store.$subscribe()` inside of a component, it will be automatically
- * cleanup up when the component gets unmounted.
+ * cleanup up when the component gets unmounted unless `detached` is set to
+ * true.
*
* @param callback - callback passed to the watcher
+ * @param detached - detach the subscription from the context this is called from
* @returns function that removes the watcher
*/
- $subscribe(callback: SubscriptionCallback<S>): () => void
+ $subscribe(callback: SubscriptionCallback<S>, detached?: boolean): () => void
/**
* @alpha Please send feedback at https://github.com/posva/pinia/issues/240
*
* It also returns a function to remove the callback. Note than when calling
* `store.$onAction()` inside of a component, it will be automatically cleanup
- * up when the component gets unmounted.
+ * up when the component gets unmounted unless `detached` is set to true.
*
* @example
*
*```
*
* @param callback - callback called before every action
+ * @param detached - detach the subscription from the context this is called from
* @returns function that removes the watcher
*/
- $onAction(callback: StoreOnActionListener<Id, S, G, A>): () => void
+ $onAction(
+ callback: StoreOnActionListener<Id, S, G, A>,
+ detached?: boolean
+ ): () => void
}
/**