CDK Lambda NodejsFunction pdfmake Types - aws-lambda

How can I get pdfmake types working in an AWS CDK NodejsFunction Lambda?
For example, I'd like VSCode to highlight invalid properties and show intelliSense on objects.
// I'd like to type the doc definition like this:
const definition: TDocumentDefinitions = { ...
// And type the styles definition like this:
const styles: StyleDictionary = { ...
// Also, if I add styles to the docDefinition I get a type error that I have to tell typescript to ignore with #ts-ignore:
// Error: Argument of type '{ content: never[]; styles: { p: { margin: number[]; }; }; }' is not assignable to parameter of type 'TDocumentDefinitions'.
const styles = {
p: { margin: [0, 0] }
};
const docDefinition = {
content: [],
styles
}
printer.createPdfKitDocument(docDefinition)
The following pdfmake code below works but is not typed!
AWS CDK code:
new lambdaNJS.NodejsFunction(this, 'MyLambdaFunction', {
...
bundling: {
...
nodeModules: ['pdfmake'], // List of modules that should NOT be bundled but instead included in the node_modules folder.
}
});
Lambda Typescript code:
import PdfPrinter = require('pdfmake');
const fonts = {
Courier: {
normal: 'Courier',
bold: 'Courier-Bold',
italics: 'Courier-Oblique',
bolditalics: 'Courier-BoldOblique'
},
Helvetica: {
normal: 'Helvetica',
bold: 'Helvetica-Bold',
italics: 'Helvetica-Oblique',
bolditalics: 'Helvetica-BoldOblique'
},
Times: {
normal: 'Times-Roman',
bold: 'Times-Bold',
italics: 'Times-Italic',
bolditalics: 'Times-BoldItalic'
},
Symbol: {
normal: 'Symbol'
},
ZapfDingbats: {
normal: 'ZapfDingbats'
}
};
const docDefinition = {
content: [
'First paragraph'
],
defaultStyle: {
font: 'Helvetica'
}
};
const printer = new PdfPrinter(fonts);
const doc = printer.createPdfKitDocument(docDefinition);
doc.end();
My package.json contains: #types/pdfmake
There's a GitHub post here https://github.com/bpampuch/pdfmake/issues/525 where someone was able to get types working like this. But it's not clear what environment they are running or if this is in nodejs or browser.
import { StyleDictionary } from 'pdfmake/interfaces';
const styles: StyleDictionary = {
...
};
The following compiles in my VSCode but when deployed to Lambda results in a pdfmake/interfaces not found error.
import pdfmakeInterfaces = require('pdfmake/interfaces');
const definition: pdfmakeInterfaces.TDocumentDefinitions = {
...
}
The pdfmake index.d.ts file uses types and exports the main PdfPrinter object but doesn't export any types.
// Type definitions for pdfmake 0.1
// Project: http://pdfmake.org
// Definitions by: Milen Stefanov <https://github.com/m1llen1um>
// Rajab Shakirov <https://github.com/radziksh>
// Enzo Volkmann <https://github.com/evolkmann>
// Andi Pätzold <https://github.com/andipaetzold>
// Neal Mummau <https://github.com/nmummau>
// Jean-Raphaël Matte <https://github.com/jeralm>
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
// TypeScript Version: 3.0
/// <reference types="pdfkit" />
import { BufferOptions, TDocumentDefinitions, TFontDictionary } from './interfaces';
declare class PdfPrinter {
constructor(fontDescriptors: TFontDictionary);
createPdfKitDocument(docDefinition: TDocumentDefinitions, options?: BufferOptions): PDFKit.PDFDocument;
}
export = PdfPrinter;

Related

How do I render embedded code blocks from Contentful with Prism in Astro?

I decided to try out Astro by building a web development blog integrated with contentful. I got everything working except for the code blocks. I create an embedded entry for the code block in my content model, which returns the following nodeType:
{
nodeType: 'embedded-entry-block',
data: {
target: {
metadata: [Object],
sys: [Object],
fields: {
code:
'```html\n<!DOCTYPE html>\n<html>\n<head>\n<title>My First Astro App</title>\n</head>\n <body>\n<h1>Hello, World!</h1>\n</body>\n</html>\n```'
}
}
},
content: []
}
I cannot figure out how to use documentToHtmlString from "#contentful/rich-text-html-renderer" to display the code block in my blog with a themed Prism component.
I passed a custom rendering component for the embedded entry to the documentToHtmlStringoptions, and extracted the lang and HTML code, but then I could not figure out how to return a themed Prism code block. However, I did manage to get it working without styling using the runHighlighterWithAstro function from the #astrojs/prism library.
---
import { documentToHtmlString } from "#contentful/rich-text-html-renderer";
import { BLOCKS } from "#contentful/rich-text-types";
import ContentLayout from "#layouts/ContentLayout.astro";
import BlogPostMeta from "#components/BlogPostMeta.astro";
import { BlogPost, contentfulClient } from "#lib/contentful";
import { Prism } from "#astrojs/prism";
import { runHighlighterWithAstro } from "#astrojs/prism/dist/highlighter";
export async function getStaticPaths() {
const entries = await contentfulClient.getEntries<BlogPost>({
content_type: "blogPost",
});
function extractCode(str: string): { lang: string; code: string } | null {
const match = str?.match(/```(\w+)\n([\s\S]+)\n```/);
if (match) {
return {
lang: match[1],
code: match[2],
};
}
return null;
}
const renderOptions = {
renderNode: {
[BLOCKS.EMBEDDED_ENTRY]: (node: any) => {
const { code } = node.data.target.fields;
const extractedCode = extractCode(code);
if (extractedCode) {
const { lang, code } = extractedCode;
const { classLanguage, html } = runHighlighterWithAstro(lang, code);
return `<pre class=${classLanguage} set:html=${html}></pre>`;
}
return "";
},
},
};
const pages = entries.items.map((item) => ({
params: { slug: item.fields.slug },
props: {
title: item.fields.title,
description: item.fields.description,
content: documentToHtmlString(item.fields.content, renderOptions),
date: new Date(item.fields.date).toLocaleDateString(),
},
}));
return pages;
}
const { slug } = Astro.params;
if (typeof slug !== "string") {
throw Error(`Slug should be a string. Received: ${slug}`);
}
const { description, content, title, date } = Astro.props;
---
<ContentLayout title={title} date={date}>
<BlogPostMeta
title={title}
description={description}
publishDate={date}
pagePath={`/posts/${slug}`}
slot='meta'
/>
<body>
<h1>{title}</h1>
<time>{date}</time>
<article slot='content' set:html={content} />
</body>
</ContentLayout>
I wanted to use a component, CodeBlockthat contains my styled Prism element. Unfortunately, I may be misunderstanding something about Astro, coming from React land.
---
import { Prism } from "#astrojs/prism";
const { language, code } = Astro.props;
---
<Prism lang={language} code={code} />
<style is:global>...
</style>

SCSS Modules not properly loading in Storybook for my Nextjs app

Working on a new project using Nextjs and Storybook. We are using SCSS modules to style our components, and they work just fine in the actual app on the browser, but they won't link up in the stories themselves. Here are a few simple snippets to show where I'm at right now:
Component:
import React from 'react'
import styles from './VideoEntryTile.module.scss'
const VideoEntryTile: React.FC = () => {
return (
// This displays properly in the browser but not storybook
<div className={styles.container}>
<p>Hello</p>
</div>
)
}
export default VideoEntryTile
SCSS module:
.container {
background-color: blueviolet;
}
Component story:
import React from 'react';
import { ComponentStory, ComponentMeta } from '#storybook/react';
import VideoEntryTile from './VideoEntryTile';
export default {
title: 'Video Entry Tile',
component: VideoEntryTile,
argTypes: {
},
} as ComponentMeta<typeof VideoEntryTile>;
const Template: ComponentStory<typeof VideoEntryTile> = (args) => <VideoEntryTile {...args} />;
export const Primary = Template.bind({});
Primary.args = {};
./storybook/main.js:
module.exports = {
core: {
builder: 'webpack5',
},
"stories": [
"../src/**/*.stories.mdx",
"../src/**/*.stories.#(js|jsx|ts|tsx)",
"../src/components/**/*.stories.tsx"
],
"addons": [
"#storybook/addon-links",
"#storybook/addon-essentials",
{
name: '#storybook/preset-scss',
options: {
sassLoaderOptions: {
modules: true
}
}
}
],
"framework": "#storybook/react"
}
Can anyone tell me where I'm going wrong? Thanks.
Create a file called scss-preset.js in .storybook:
// Copied from https://github.com/storybookjs/presets/blob/master/packages/preset-scss/index.js
function wrapLoader(loader, options) {
if (options === false) {
return [];
}
return [
{
loader,
options,
},
];
}
function webpack(webpackConfig = {}, options = {}) {
const { module = {} } = webpackConfig;
const {
styleLoaderOptions,
cssLoaderOptions,
sassLoaderOptions,
rule = {},
} = options;
return {
...webpackConfig,
module: {
...module,
rules: [
...(module.rules || []),
{
test: /(?<!module)\.s[ca]ss$/,
...rule,
use: [
...wrapLoader('style-loader', styleLoaderOptions),
...wrapLoader('css-loader', {
...cssLoaderOptions,
modules: false,
}),
...wrapLoader('sass-loader', sassLoaderOptions),
],
},
{
test: /\.module\.s[ca]ss$/,
...rule,
use: [
...wrapLoader('style-loader', styleLoaderOptions),
...wrapLoader('css-loader', {
...cssLoaderOptions,
modules: true,
}),
...wrapLoader('sass-loader', sassLoaderOptions),
],
},
],
},
};
}
module.exports = { webpack };
This is going through each scss file and, if it's a module.scss file, processing it differently than the ones that aren't.
in main.js, add presets: [path.resolve("./.storybook/scss-preset.js")], to module.exports. Should be all you need.
Make sure you have installed sass
npm install sass --save
Install scss preset
npm install #storybook/preset-scss --save
Add it to the addons list inside your .storybook/main.js
addons: ['#storybook/preset-scss',]

Gatsby Contentful embedded image

As I see there is no json option anymore when querying the contentfulBlogPost only raw. I was able to make some changes to get everything from the body, except the image from that post.
If I made a test in GraphQL Playground I can get the image id and url but that's it.
query {
allContentfulAsset {
edges {
node {
id
file {
url
}
}
}
}
}
I tried to find an example how to get embedded images but no luck....
import React from 'react'
import { graphql } from 'gatsby'
import { documentToReactComponents } from '#contentful/rich-text-react-renderer'
import Layout from '../components/layout'
export const query = graphql`
query($slug: String!) {
contentfulBlogPost(slug: {eq: $slug}) {
title
publishedDate(formatString: "MMMM Do, YYYY")
body {
raw
}
}
allContentfulAsset {
edges {
node {
id
file {
url
}
}
}
}
}
`
const Blog = (props) => {
const options = {
renderNode: {
"embedded-asset-block": (node) => {
const alt = node.data.title
const url = node.file.url
return <img alt={alt} src={url}/>
}
}
}
return (
<Layout>
<h1>{props.data.contentfulBlogPost.title}</h1>
<p>{props.data.contentfulBlogPost.publishedDate}</p>
{documentToReactComponents(JSON.parse(props.data.contentfulBlogPost.body.raw, options))}
</Layout>
)
}
export default Blog
Plugins:
...
'gatsby-plugin-sharp',
{
resolve: 'gatsby-transformer-remark',
options: {
plugins: [
'gatsby-remark-relative-images',
{
resolve: 'gatsby-remark-images-contentful',
options: {
maxWidth: 750,
linkImagesToOriginal: false
}
}
]
}
}
],
}
Hi I saw this solution in a Youtube comment. First thing you have to do is change your Graphql query to something like this:
query ($slug: String!) {
contentfulBlogPost(slug: {eq: $slug}) {
id
title
publishedDate(formatString: "MMMM Do, YYYY")
body {
raw
references {
... on ContentfulAsset {
contentful_id
title
file {
url
}
}
}
}
}
}
Then change your options constant to:
const options = {
renderNode: {
[BLOCKS.EMBEDDED_ASSET]: node => {
console.log(node);
const imageID = node.data.target.sys.id;
const {
file: {url},
title
} = props.data.contentfulBlogPost.body.references.find(({contentful_id: id}) => id === imageID);
return <img src={url} alt={title} />
}
}
}
Use something like:
import { BLOCKS, MARKS } from "#contentful/rich-text-types"
import { renderRichText } from "gatsby-source-contentful/rich-text"
​
const Bold = ({ children }) => <span className="bold">{children}</span>
const Text = ({ children }) => <p className="align-center">{children}</p>
​
const options = {
renderMark: {
[MARKS.BOLD]: text => <Bold>{text}</Bold>,
},
renderNode: {
[BLOCKS.PARAGRAPH]: (node, children) => <Text>{children}</Text>,
[BLOCKS.EMBEDDED_ASSET]: node => {
return (
<>
<h2>Embedded Asset</h2>
<pre>
<code>{JSON.stringify(node, null, 2)}</code>
</pre>
</>
)
},
},
}
​
renderRichText(node.bodyRichText, options)
Source: https://www.contentful.com/developers/docs/tutorials/general/rich-text-and-gatsby/
The return statement in BLOCKS.EMBEDDED_ASSET entry will contain your data, adapt to your needs. If you go inside the dependency, you'll see all the exposed methods, so you will have also a BLOCKS.EMBEDDED_ENTRY entry for your embedded entries. Apply it like:
[BLOCKS.EMBEDDED_ENTRY]: node => {
// your logic to manipulate the entry here
return (
<>
<div>whatever</div>
</>
)
},
For anyone that is still struggling to find the "references" field in graphql, remember that you HAVE TO first create an entry in contentful by adding at least one image. Otherwise, the references field will not show up in graphql, hence you can not query it.

TypeScriptError: Type 'Data' is not assignable to type 'string'

I am using React-typescript for my app. for state management I am using Redux-toolkit. I am fetching one open api and store it my redux store. I created dispatch function. From the component when I click the dispatch function then it will display random dog image. But the problem is after mapping the when I am using this img src. I am getting typescript error: Type 'Data' is not assignable to type 'string'. I don't know what I am doing wrong. i uploaded my code in codesandbox, although it works in codesandbox but does not work in my app.
Ps. I did not upload my store setup code because it works find ☺️.
This is my reducer
/* eslint-disable #typescript-eslint/indent */
import { createSlice, PayloadAction } from '#reduxjs/toolkit';
import { AppThunk } from "store/store";
interface IMeta {
loading: boolean;
error: boolean;
message: string;
}
interface Data {
src: string;
}
interface IDogs {
meta: IMeta;
dogs: Data[];
}
const initialState: IDogs = {
"meta": {
"loading": false,
"error": false,
"message": ``
},
"dogs": []
};
const dogSlice = createSlice({
"name": `random-dogs`,
initialState,
"reducers": {
loadState(state) {
state.meta = {
"loading": true,
"error": false,
"message": ``
};
state.dogs = [];
},
fetchData(state, action: PayloadAction<Data[]>) {
state.meta.loading = false;
state.dogs = action.payload;
console.log(`dogs`, action.payload);
},
loadFailed(state, action: PayloadAction<string>) {
state.meta = {
"loading": false,
"error": true,
"message": action.payload
};
state.dogs = [];
}
}
});
export const { loadState, fetchData, loadFailed } = dogSlice.actions;
export default dogSlice.reducer;
export const fetchDogs = (): AppThunk => async (dispatch) => {
const url = `https://dog.ceo/api/breeds/image/random/5`;
try {
dispatch(loadState);
const response = await fetch(url);
const data = await response.json();
console.log(data);
console.log(data.message);
const singleData = data.message.map((i) => i);
dispatch(fetchData(singleData));
} catch (error) {
dispatch(loadFailed(`dogs are unavailable`));
console.log({ error });
}
};
This is the component I am using the redux store
import React, { memo } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { fetchDogs } from 'store/dogs';
import { RootState } from 'store/combineReducer';
export default memo(() => {
const state = useSelector((rootState: RootState) => ({
"dogs": rootState.fetchDogs.dogs,
"meta": rootState.fetchDogs.meta
}));
const dispatch = useDispatch();
console.log(`Dog component`, state.dogs[0]);
return (
<div>
{state.meta.loading ? <p>loading....</p> :
state.dogs.map((i, index) =>
<div key={index}>
<ul>
<li>{i}</li> // I can see the strings
</ul>
<img style={{ "width": 50, "height": 50 }} src={i} /> //getting error in here
</div>)}
<br></br>
<button onClick={() => dispatch(fetchDogs())}> display random dogs</button>
</div>
);
});
The situation is as follows:
Interface IDog is has a property "dogs" of type Data[].
Data has a property "src" of type String.
Src attribute of an img needs to be a string.
You are now passing IDogs.dogs. You need to go deeper to IDogs.dogs.src to get the source string you want.
So line 25 of App.tsx should look like this and all seems to work fine:
<img style={{ width: 50, height: 50 }} src={i.src} alt="dog" />
PS: The codesandbox example still works as it apparently does some kind of assumption that you want the src property, but as you see you still get the error.
EDIT: After some fiddling the answer is as below. It is however connected to what was written above.
I downloaded you project and tried to run in npm on my PC. I did 2 things to make it work:
I updated line 25 to use the cast: src={String(i)}
I updated react-scripts. See this thread for reference: TypeError [ERR_INVALID_ARG_TYPE]: The "path" argument must be of type string. Received type undefined raised when starting react app

Integrate jsPDF in Nativescript

I researched that using jsPDF, json response cna be converted into PDF and then can be downloaded. Is there any way to integrate JsPDF in nativescript application?
Check the following POC with jsPDF and NativeScript, you need to override some global variables required by jsPDF and install some packages from npm:
global['window'] = {
'document': {
'createElementNS': () => { return {} }
}
};
global['document'] = {
'createElement': (str) => { return {} }
};
global['navigator'] = {};
import * as base64 from 'base-64';
import * as utf8 from 'utf8';
import * as jsPDF from 'jspdf';
global['btoa'] = (str) => {
var bytes = utf8.encode(str);
return base64.encode(bytes);
};
Example: https://play.nativescript.org/?template=play-ng&id=OGrw9s&v=8
By other hand, you can use a NativeScript shim for aotb / btoa functions, to avoid install more packages from npm (Only install jspdf and #types/jspdf): https://play.nativescript.org/?template=play-ng&id=OGrw9s&v=13
As you can see in the logic of the component, you need to import the file and modify some global variables:
require('../base64')
global['window'] = {
'document': {
'createElementNS': () => { return {} }
},
'atob': global['atob']
};
global['document'] = {
'createElement': (str) => { return {} }
};
global['navigator'] = {};
It would be much better to create a fork of jsPDF with that solution.
And later you can create your PDF:
generatePDF() {
var doc = new jsPDF('p', 'pt');
doc.setFontSize(26);
doc.text(40, 50, "My first PDF with NativeScript!");
//Add image
doc.addImage(imgData, 'JPEG', 20, 90, 300, 300)
var base64 = doc.output('datauristring')
dialogs.alert({
title: "PDF - Base64",
message: base64,
okButtonText: "Copy text"
}).then(() => {
clipboard.setText(base64)
});
}

Resources