@knapsack/app
Version:
Build Design Systems on top of knapsack, by Basalt
332 lines (309 loc) • 7.98 kB
text/typescript
/**
* Copyright (C) 2018 Basalt
This file is part of Knapsack.
Knapsack is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by the Free
Software Foundation; either version 2 of the License, or (at your option)
any later version.
Knapsack is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
more details.
You should have received a copy of the GNU General Public License along
with Knapsack; if not, see <https://www.gnu.org/licenses>.
*/
import { DocumentNode } from 'graphql';
import md5 from 'md5';
import { apiUrlBase, graphqlBase } from '../lib/constants';
import { createDemoUrl } from '../lib/routes';
import { KnapsackTemplateDemo } from '../schemas/patterns';
import * as Files from '../schemas/api/files';
import * as Plugins from '../schemas/api/plugins';
import { AppState } from './store';
import {
KsRenderResults,
KsTemplateRenderedMeta,
KsTemplateUrls,
} from '../schemas/knapsack-config';
import { timer } from '../lib/utils';
import { FileResponse } from '../schemas/misc';
import { PatternRenderData } from '../schemas/api/render';
export { Files };
/**
* GraphQL Query Object to String
* @param gqlQueryObject - GraphQL query made from `gql` - https://github.com/apollographql/graphql-tag/issues/150
* @return {string}
*/
export function gqlToString(gqlQueryObject: DocumentNode): string {
return gqlQueryObject.loc.source.body;
}
export interface GraphQlResponse {
data?: any;
errors?: {
message: string;
extensions: {
code: string;
stacktrace: string[];
};
locations: {
line: number;
column: number;
}[];
}[];
}
/**
* GraphQL Query
* Must pass in `query` OR `gqlQuery`
*/
export function gqlQuery({
query,
gqlQueryObj,
variables = {},
}: {
/**
* Plain GraphQL query
*/
query?: string | DocumentNode;
/**
* GraphQL query made from `gql`
*/
gqlQueryObj?: DocumentNode;
/**
* GraphQL variables
*/
variables?: object;
}): Promise<GraphQlResponse> {
if (!query && !gqlQueryObj) {
throw new Error('Must provide either "query" or "gqlQueryObj".');
}
if (typeof query !== 'string') {
if (gqlQueryObj.kind !== 'Document') {
throw new Error('"gqlQueryObj" not a valid GraphQL document.');
}
// get the plain string from the `gql` parsed object
query = gqlToString(gqlQueryObj); // eslint-disable-line no-param-reassign
}
return window
.fetch(graphqlBase, {
method: 'POST',
headers: {
'Accept-Encoding': 'gzip, deflate, br',
'Content-Type': 'application/json',
Accept: 'application/json',
Connection: 'keep-alive',
Dnt: '1',
},
body: JSON.stringify({
query,
variables,
}),
})
.then(res => res.json())
.catch(console.log.bind(console));
}
type KnapsackDesignToken = import('@knapsack/core/types').KnapsackDesignToken;
export function getDesignTokens(): Promise<KnapsackDesignToken[]> {
return window.fetch(`${apiUrlBase}/design-tokens`).then(res => res.json());
}
export function files(x: Files.Actions): Promise<Files.ActionResponses> {
return window
.fetch(Files.endpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Accept: 'application/json',
},
body: JSON.stringify(x),
})
.then(res => {
if (!res.ok) {
const { status, statusText } = res;
console.error(`Error in calling files endpoint`, {
status,
statusText,
});
}
return res.json();
});
}
export function getPluginContent({
pluginId,
}: {
pluginId: string;
}): Promise<Plugins.GetContentResponse> {
const body: Plugins.GetContentRequest = {
pluginId,
type: Plugins.ACTIONS.getContent,
};
return fetch(Plugins.endpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Accept: 'application/json',
},
body: JSON.stringify(body),
}).then(res => res.json());
}
/**
* Save data up on server to be used in template rendering with `dataId` query param later
* @returns dataId that is md5 hash
*/
export function saveData(data: object): Promise<string> {
return window
.fetch(`${apiUrlBase}/data`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Accept: 'application/json',
},
body: JSON.stringify(data),
})
.then(res => res.json())
.then(results => {
if (!results.ok) {
console.error(
`Uh oh! Tried to save data by uploading it to '${apiUrlBase}/data' with no luck.`,
{
data,
results,
},
);
}
return results.data.hash;
})
.catch(console.log.bind(console));
}
export function uploadFile(file: File): Promise<FileResponse> {
const body = new FormData();
body.append('file', file);
return window
.fetch(`${apiUrlBase}/upload`, {
method: 'POST',
headers: {
Accept: 'application/json',
},
body,
})
.then(res => res.json())
.then(response => {
if (!response.ok) {
console.error('uh oh: upload crap out', response);
}
return response;
});
}
export function getInitialState(): Promise<AppState> {
const getTime = timer();
return window
.fetch(`${apiUrlBase}/data-store`)
.then(res => res.json())
.then(initialState => {
console.debug(`KS: initial state fetch took: ${getTime()}s`);
// console.log({ initialState });
return initialState;
})
.catch(console.log.bind(console));
}
export function renderTemplate(
options: PatternRenderData,
): Promise<KsRenderResults> {
return window
.fetch(`${apiUrlBase}/render`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Accept: 'application/json',
},
body: JSON.stringify(options),
})
.then(res => res.json())
.catch(console.log.bind(console));
}
const prevDataIds = new Map<string, string>();
/**
* Get a URL where this Pattern's Template can be viewed
*/
export async function getTemplateUrls({
patternId,
templateId,
assetSetId,
demo,
cacheBuster,
}: {
patternId: string;
templateId?: string;
assetSetId?: string;
demo: KnapsackTemplateDemo;
cacheBuster?: string;
}): Promise<{
dataId: string;
urls: KsTemplateUrls;
}> {
const hash = md5(JSON.stringify(demo));
const dataId = prevDataIds.has(hash)
? prevDataIds.get(hash)
: await saveData(demo);
if (!prevDataIds.has(hash)) {
prevDataIds.set(hash, dataId);
}
return {
dataId,
urls: {
external: createDemoUrl({
patternId,
templateId,
assetSetId,
dataId,
cacheBuster,
isInIframe: false,
wrapHtml: true,
}),
iframeSrc: createDemoUrl({
patternId,
templateId,
assetSetId,
dataId,
cacheBuster,
isInIframe: true,
wrapHtml: true,
}),
},
};
}
/**
* Get a URL where this Pattern's Template can be viewed
*/
export async function getTemplateInfo({
patternId,
templateId,
assetSetId,
demo,
cacheBuster,
}: {
patternId: string;
templateId?: string;
assetSetId?: string;
demo: KnapsackTemplateDemo;
cacheBuster?: string;
}): Promise<KsTemplateRenderedMeta> {
const { dataId, urls } = await getTemplateUrls({
patternId,
templateId,
assetSetId,
demo,
cacheBuster,
});
const renderResults = await renderTemplate({
patternId,
templateId,
dataId,
assetSetId,
isInIframe: false,
wrapHtml: false,
});
return {
...renderResults,
urls,
dataId,
};
}