Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Proof of concept - Offline browsing #1336

Draft
wants to merge 4 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 61 additions & 0 deletions packages/shopware-6-client/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion packages/shopware-6-client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@
"query-string": "^6.13.7"
},
"devDependencies": {
"@types/query-string": "^6.3.0"
"@types/query-string": "^6.3.0",
"idb": "^6.0.0"
},
"license": "MIT",
"publishConfig": {
Expand Down
45 changes: 45 additions & 0 deletions packages/shopware-6-client/src/index.inner.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { defaultInstance, ConfigChangedArgs } from "./apiService";
import { ClientSettings } from "./settings";
export { ClientSettings } from "./settings";
export {
createInstance,
ConfigChangedArgs,
ShopwareApiInstance,
} from "./apiService";

export * from "./services/categoryService";
export * from "./services/productService";
export * from "./services/customerService";
export * from "./services/contextService";
export * from "./services/cartService";
export * from "./services/navigationService";
export * from "./services/pageService";
export * from "./services/checkoutService";
export * from "./services/pluginService";
export * from "./services/searchService";
export * from "./services/formsService";
export * from "./endpoints";

/**
* @beta
*/
export const config: ClientSettings = defaultInstance.config;
/**
* Setup configuration. Merge default values with provided in param.
* This method will override existing config. For config update invoke **update** method.
* @beta
*/
export const setup: (config?: ClientSettings) => void = defaultInstance.setup;

/**
* Update current configuration. This will change only provided values.
* @beta
*/
export const update: (config?: ClientSettings) => void = defaultInstance.update;

/**
* @beta
*/
export const onConfigChange: (
fn: (context: ConfigChangedArgs) => void
) => void = defaultInstance.onConfigChange;
57 changes: 16 additions & 41 deletions packages/shopware-6-client/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,45 +1,20 @@
import { defaultInstance, ConfigChangedArgs } from "./apiService";
import { ClientSettings } from "./settings";
export { ClientSettings } from "./settings";
export {
createInstance,
ConfigChangedArgs,
ShopwareApiInstance,
} from "./apiService";
export * from "./index.inner";

export * from "./services/categoryService";
export * from "./services/productService";
export * from "./services/customerService";
export * from "./services/contextService";
export * from "./services/cartService";
export * from "./services/navigationService";
export * from "./services/pageService";
export * from "./services/checkoutService";
export * from "./services/pluginService";
export * from "./services/searchService";
export * from "./services/formsService";
export * from "./endpoints";
// Explicitly import offline overrides and alias them

/**
* @beta
*/
export const config: ClientSettings = defaultInstance.config;
/**
* Setup configuration. Merge default values with provided in param.
* This method will override existing config. For config update invoke **update** method.
* @beta
*/
export const setup: (config?: ClientSettings) => void = defaultInstance.setup;
// Product Listing
import { getCategoryProducts as offlineGetCategoryProducts } from "./offline-services/productService";

/**
* Update current configuration. This will change only provided values.
* @beta
*/
export const update: (config?: ClientSettings) => void = defaultInstance.update;
const getCategoryProducts = offlineGetCategoryProducts;

/**
* @beta
*/
export const onConfigChange: (
fn: (context: ConfigChangedArgs) => void
) => void = defaultInstance.onConfigChange;
// Product Search
import {
searchProducts as offlineSearchProducts,
searchSuggestedProducts as offlineSearchSuggestedProducts,
} from "./offline-services/searchService";

const searchProducts = offlineSearchProducts;
const searchSuggestedProducts = offlineSearchSuggestedProducts;

// Re-export them with original name, because local modules have priority in bundler
export { searchProducts, searchSuggestedProducts, getCategoryProducts };
73 changes: 73 additions & 0 deletions packages/shopware-6-client/src/offline-services/productService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import * as innerClient from "./../index.inner";

import { ShopwareSearchParams } from "@shopware-pwa/commons/interfaces/search/SearchCriteria";
import { ProductListingResult } from "@shopware-pwa/commons/interfaces/response/ProductListingResult";
import { Product } from "@shopware-pwa/commons/interfaces/models/content/product/Product";

import { defaultInstance, ShopwareApiInstance } from "../apiService";

import { open } from "../offline/store/DatabaseHandle";
import { IDBPDatabase } from "idb";

import { createProductListingResult } from "../offline/factory/ProductListingResultFactory";

import { synchroniseProducts } from "../offline/sync/ProductSynchroniser";

/**
* Get default amount of products and listing configuration for given category
*
* @throws ClientApiError
* @beta
*/
export const getCategoryProducts = async function (
categoryId: string,
criteria?: ShopwareSearchParams,
contextInstance: ShopwareApiInstance = defaultInstance
): Promise<ProductListingResult> {
let db: IDBPDatabase;

try {
db = await open();
// throw new Error("e");
} catch (e) {
// await synchroniseProducts(1)
// await synchroniseProducts(2)
// await synchroniseProducts(3)
// await synchroniseProducts(4)
return innerClient.getCategoryProducts(
categoryId,
criteria,
contextInstance
);
}

let keyRange = IDBKeyRange.only(categoryId);

let total = await db
.transaction("product_search_data")
.store.index("category")
.count(keyRange);
let cursor = await db
.transaction("product_search_data")
.store.index("category")
.openCursor(keyRange);

let products: Array<Product> = [];

let limit: number = criteria?.limit || 10;
let page: number = criteria?.p || 1;
let start: number = (page - 1) * limit;

// If start is 0, do nothing, otherwise advance to start
cursor = start > 0 ? (await cursor?.advance(start)) || null : cursor;

let count = 0;

while (cursor && count < limit) {
products.push(cursor.value);
cursor = await cursor.continue();
count++;
}

return createProductListingResult(products, total, limit, page);
};
62 changes: 62 additions & 0 deletions packages/shopware-6-client/src/offline-services/searchService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import * as innerClient from "./../index.inner";

import { open } from "./../offline/store/DatabaseHandle";
import { handleRequest } from "./../offline/criteria/RequestCriteriaHandler";

import { ProductListingResult } from "@shopware-pwa/commons/interfaces/response/ProductListingResult";
import { ShopwareSearchParams } from "@shopware-pwa/commons/interfaces/search/SearchCriteria";
import { defaultInstance, ShopwareApiInstance } from "./../apiService";

import { IDBPDatabase } from "idb";

export async function searchProducts(
criteria?: ShopwareSearchParams,
contextInstance: ShopwareApiInstance = defaultInstance
): Promise<ProductListingResult> {
let db: IDBPDatabase;

try {
db = await open();
} catch (e) {
return innerClient.searchProducts(criteria, contextInstance);
}

let elements = await db.transaction("product").store.getAll();

/* This is where the magic happens */
let productResult: ProductListingResult = await handleRequest(
elements,
criteria
);

return productResult;
}

/**
* Search for suggested products based on criteria.
* From: Shopware 6.4
*
* @beta
*/
export async function searchSuggestedProducts(
criteria?: ShopwareSearchParams,
contextInstance: ShopwareApiInstance = defaultInstance
): Promise<ProductListingResult> {
let db: IDBPDatabase;

try {
db = await open();
} catch (e) {
return innerClient.searchSuggestedProducts(criteria, contextInstance);
}

let elements = await db.transaction("product").store.getAll();

/* This is where the magic happens */
let productResult: ProductListingResult = await handleRequest(
elements,
criteria
);

return productResult;
}
22 changes: 22 additions & 0 deletions packages/shopware-6-client/src/offline/criteria/CriteriaBuilder.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { ShopwareSearchParams } from "@shopware-pwa/commons/interfaces/search/SearchCriteria";

import FilterInterface from "./filter/FilterInterface";

import TermFilter from "./filter/TermFilter";
// import RangeFilter from "./filter/RangeFilter"
// import EqualsFilter from "./filter/EqualsFilter"

const buildFilters = function (
criteria: ShopwareSearchParams,
filters: Array<FilterInterface> = []
): Array<FilterInterface> {
console.log(criteria);

filters.push(new TermFilter(criteria?.query || ""));
// filters.push(new RangeFilter('ratingAverage', { gt: 3 }))
// filters.push(new EqualsFilter('id', 'c6a351be9ad54596b1062196f7fd7240'))

return filters;
};

export { buildFilters };
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { ShopwareSearchParams } from "@shopware-pwa/commons/interfaces/search/SearchCriteria";

import { ProductListingResult } from "@shopware-pwa/commons/interfaces/response/ProductListingResult";
import { Product } from "@shopware-pwa/commons/interfaces/models/content/product/Product";

import { createProductListingResult } from "../factory/ProductListingResultFactory";

import { buildFilters } from "./CriteriaBuilder";

import FilterInterface from "./filter/FilterInterface";

const handleRequest = async function (
allElements: Array<Product>,
criteria?: ShopwareSearchParams
): Promise<ProductListingResult> {
let filters: Array<FilterInterface> = [];
if (typeof criteria !== "undefined") {
filters = buildFilters(criteria);
}

/* Build Criteria */

console.time("parseCriteria");

/* Init empty result set */
let elements: Array<Product> = [];

if (typeof criteria !== "undefined" && criteria.query) {
/* Execute filters */
elements = _applyFilters(filters, allElements);
}

console.timeEnd("parseCriteria");

return createProductListingResult(elements, criteria);
};

/* Simple method for filtering */
const _applyFilters = function (
filters: Array<FilterInterface>,
elements: Array<Product>
): Array<Product> {
let filteredElements = elements.filter((product: Product) => {
return filters
.map((filter): boolean => {
return filter.supports(product) && filter.match(product);
})
.reduce((evaluated, current) => {
return evaluated && current;
});
});

return filteredElements;
};

export { handleRequest };
Loading