diff --git a/frontend/src/components/ResourceDialog.vue b/frontend/src/components/ResourceDialog.vue index ddbe4942d..6b1d92faa 100644 --- a/frontend/src/components/ResourceDialog.vue +++ b/frontend/src/components/ResourceDialog.vue @@ -144,6 +144,13 @@ + + ({ ...emptyResource }) diff --git a/frontend/src/data/studioResources.ts b/frontend/src/data/studioResources.ts index 775e4e808..bd3cc0381 100644 --- a/frontend/src/data/studioResources.ts +++ b/frontend/src/data/studioResources.ts @@ -7,6 +7,7 @@ export const studioPageResources = createListResource({ "resource_type", "resource_name", "auto", + "cache", "fields", "filters", "limit", diff --git a/frontend/src/pages/AppContainer.vue b/frontend/src/pages/AppContainer.vue index 850baedca..cdacc9836 100644 --- a/frontend/src/pages/AppContainer.vue +++ b/frontend/src/pages/AppContainer.vue @@ -24,39 +24,47 @@ const codeStore = useCodeStore() const page = ref(null) const rootBlock = ref(null) +let renderedPath: string | null = null -async function loadPage() { - let { pageRoute } = route.params as { pageRoute: string[] } - const isDynamic = route.meta?.isDynamic - - let currentPath = "/" - if (isDynamic) { - currentPath = route.matched?.[0]?.path - } else if (pageRoute) { - currentPath = pageRoute[0] - } - +async function loadPage(force: boolean = false) { + const currentPath = resolvePagePath() if (!currentPath) { rootBlock.value = null + renderedPath = null return } - page.value = await findPageWithRoute(window.app_name, currentPath) + const isSamePage = !force && currentPath === renderedPath + if (!isSamePage) { + const nextPage = await findPageWithRoute(window.app_name, currentPath) + if (!nextPage) return + page.value = nextPage + } if (!page.value) return + await store.setPageData(page.value) await codeStore.setPageScript(page.value, Boolean(page.value.is_standard)) + if (isSamePage) return + const blocks = window.is_preview ? JSON.parse(page.value?.draft_blocks || page.value?.blocks) : JSON.parse(page.value?.blocks) if (blocks) { rootBlock.value = getBlockInstance(blocks[0]) + renderedPath = currentPath } } -watch(() => route.path, loadPage, { immediate: true }) +watch(() => route.path, () => loadPage(), { immediate: true }) -if (window.is_preview) useLivePreview(page, loadPage) +if (window.is_preview) useLivePreview(page, () => loadPage(true)) + +function resolvePagePath() { + if (route.meta?.isDynamic) return route.matched?.[0]?.path + const { pageRoute } = route.params as { pageRoute: string[] } + return pageRoute ? pageRoute[0] : "/" +} usePageMeta(() => { return { diff --git a/frontend/src/stores/codeStore.ts b/frontend/src/stores/codeStore.ts index ba7e836ce..7a49248a2 100644 --- a/frontend/src/stores/codeStore.ts +++ b/frontend/src/stores/codeStore.ts @@ -13,7 +13,7 @@ import * as globalUtils from "@/utils/globalUtils" import { getInitialVariableValue, getValueFromObject, setValueInObject } from "@/utils/helpers" import { isDynamicValue, normalizeDynamicValue } from "@/utils/code" import { isFunctionExpression, toOptionalChaining, getTopLevelBindings } from "@/utils/parseCode" -import type { Filters, Resource, DocumentResource, DataResult } from "@/types/Studio/StudioResource" +import type { Filters, Resource, APIResource, DocumentResource, DataResult } from "@/types/Studio/StudioResource" import type { StudioPage } from "@/types/Studio/StudioPage" import type { Variable } from "@/types/Studio/StudioPageVariable" import type { ExpressionEvaluationContext } from "@/types" @@ -55,7 +55,6 @@ const useCodeStore = defineStore("codeStore", () => { async function setPageResources(page: StudioPage, setResourceConfig: boolean = false) { studioPageResources.filters = { parent: page.name } await studioPageResources.reload() - resources.value = {} const resourcePromises = studioPageResources.data.map(async (resource: Resource) => { const newResource = await getNewResource(resource, { @@ -73,14 +72,16 @@ const useCodeStore = defineStore("codeStore", () => { const resolvedResources = await Promise.all(resourcePromises) + const nextResources: Record = {} resolvedResources.forEach((item) => { - resources.value[item.resource_name] = item.value + nextResources[item.resource_name] = item.value if (setResourceConfig) { if (!item.value) return - resources.value[item.resource_name].resource_id = item.resource_id - resources.value[item.resource_name].resource_type = item.resource_type + nextResources[item.resource_name].resource_id = item.resource_id + nextResources[item.resource_name].resource_type = item.resource_type } }) + resources.value = nextResources } async function setPageVariables(page: StudioPage) { @@ -449,7 +450,7 @@ const useCodeStore = defineStore("codeStore", () => { switch (resource.resource_type) { case "Document": return getDocumentResource(resource, context) - case "Document List": + case "Document List": { const params: any = { doctype: resource.document_type, fields: fields.length ? fields : "*", @@ -463,18 +464,27 @@ const useCodeStore = defineStore("codeStore", () => { params["orderBy"] = `${resource.sort_field} ${resource.sort_order}` } return createListResource(params) - case "API Resource": + } + case "API Resource": { + const apiParams = getAPIParams(resource.params, context) return createResource({ url: resource.url, method: resource.method, - params: getAPIParams(resource.params, context), + params: apiParams, auto: resource.auto, + cache: getCacheOption(resource, apiParams), ...getTransforms(resource), ...getSuccessErrorHandlers(resource), }) + } } } + function getCacheOption(resource: APIResource, params: Record | string | null) { + if (!resource.cache) return undefined + return [resource.resource_name, resource.url, resource.method, JSON.stringify(params)] + } + function getAPIParams(params: Record | string | null = null, context: ExpressionEvaluationContext) { if (!params) return null if (typeof params === "string") { diff --git a/frontend/src/types/Studio/StudioResource.ts b/frontend/src/types/Studio/StudioResource.ts index 0c6e7c931..a71f9ee8a 100644 --- a/frontend/src/types/Studio/StudioResource.ts +++ b/frontend/src/types/Studio/StudioResource.ts @@ -9,6 +9,8 @@ interface BaseResource { resource_type: ResourceType /** Whether to automatically fetch data on first load */ auto?: boolean + /** Whether to reuse the resource and its data across page loads, keyed on url + params */ + cache?: boolean transform?: string | null on_success?: string on_error?: string diff --git a/studio/studio/doctype/studio_page_resource/studio_page_resource.json b/studio/studio/doctype/studio_page_resource/studio_page_resource.json index 3494c9c17..47016e351 100644 --- a/studio/studio/doctype/studio_page_resource/studio_page_resource.json +++ b/studio/studio/doctype/studio_page_resource/studio_page_resource.json @@ -14,6 +14,7 @@ "sort_field", "sort_order", "auto", + "cache", "document_resource_section", "document_type", "column_break_mkdu", @@ -173,6 +174,13 @@ "fieldtype": "Check", "in_list_view": 1, "label": "Auto fetch data on load" + }, + { + "default": "0", + "description": "Reuse this resource and its data across page loads, keyed on url + params", + "fieldname": "cache", + "fieldtype": "Check", + "label": "Cache data across page loads" } ], "grid_page_length": 50,