Skip to content

Commit

Permalink
It was added business functional
Browse files Browse the repository at this point in the history
  • Loading branch information
urdenko committed Apr 30, 2020
1 parent 1e989a3 commit 4d871b1
Show file tree
Hide file tree
Showing 8 changed files with 326 additions and 6 deletions.
77 changes: 76 additions & 1 deletion dev/index.html
Original file line number Diff line number Diff line change
@@ -1,8 +1,83 @@
<html>
<head>
<script type="text/javascript" src="main.js"></script>
<style>
body {
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
}

input,
textarea,
button {
width: 300px;
}

textarea,
input,
button {
margin-bottom: 20px;
}

wanted-print-form {
height: 0;
overflow: hidden;
}

@media print {
input,
textarea,
button {
display: none;
}

wanted-print-form {
height: unset;
}
}
</style>
</head>
<body>
<wanted-print-form></wanted-print-form>
<input placeholder="Input name" type="text" id="wanted-name" />
<input type="file" accept="image/*" id="wanted-image" />
<textarea rows="10" placeholder="Input his atrocity crimes" id="atrocity-crimes"></textarea>
<input placeholder="Reward" type="text" id="reward" />
<button onclick="printWanted()">Print Wanted!</button>

<script type="text/javascript">
function printWanted() {
const name = document.getElementById('wanted-name').value;
const images = document.getElementById('wanted-image').files;
const atrocityCrimes = document.getElementById('atrocity-crimes').value;
const reward = document.getElementById('reward').value;

const printForm = document.createElement('wanted-print-form');
printForm.wantedName = name;
printForm.image = images[0];
printForm.atrocityCrimes = atrocityCrimes;
printForm.reward = reward;

window.onafterprint = function () {
printForm.remove();
};

printForm.addEventListener(printForm.readyEventName, () => {
printForm.removeEventListener(printForm.readyEventName, null);
printForm.removeEventListener(printForm.errorEventName, null);
window.print();
});

printForm.addEventListener(printForm.errorEventName, (event) => {
printForm.removeEventListener(printForm.readyEventName, null);
printForm.removeEventListener(printForm.errorEventName, null);
printForm.remove();
console.error('Wanted form error:', event.detail);
});

document.body.appendChild(printForm);
}
</script>
</body>
</html>
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"homepage": "https://github.com/urdenko/print-wanted#readme",
"main": "dist/main.js",
"devDependencies": {
"@types/css-font-loading-module": "0.0.4",
"@typescript-eslint/eslint-plugin": "^2.27.0",
"@typescript-eslint/parser": "^2.27.0",
"eslint": "^6.8.0",
Expand All @@ -29,6 +30,8 @@
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^4.2.1",
"eslint-plugin-standard": "^4.0.1",
"html-loader": "^1.1.0",
"ts-loader": "^7.0.1",
"typescript": "^3.8.3",
"webpack": "^4.42.1",
"webpack-cli": "^3.3.11",
Expand Down
38 changes: 38 additions & 0 deletions src/font-loader.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
export class FontLoader {
public static async loadAllFonts(fonts: string[], text: string[]): Promise<void> {
const fontRequest = fonts.map((font) => `family=${font}`);
const normalizeFontText = this.normalizeFontText(text);

const fontResponse = await fetch(`https://fonts.googleapis.com/css2?${fontRequest.join('&')}&display=swap&text=${normalizeFontText}`);
if (!fontResponse.ok) {
return;
}

const rawText = (await fontResponse.text()).replace(/\n/g, '');
const fontSections = rawText.match(/@font-face \{.+?\}/g);

if (!fontSections) {
return;
}

await Promise.all(fontSections.map(this.loadFont));
}

private static normalizeFontText(strings: string[]): string {
const uniqueChars = new Set(strings.join('').split(''));
return Array.from(uniqueChars).join('').replace(/\s+/, '');
}

private static async loadFont(fontSection: string) {
const fontUrl = fontSection.match(/url\(.+?\)/)?.[0];
const fontName = fontSection.match(/font-family:\s*'(.+?)';/)?.[1];

if (!fontUrl || !fontName) {
return;
}

const font = new FontFace(fontName, fontUrl);
await font.load();
document.fonts.add(font);
}
}
4 changes: 4 additions & 0 deletions src/html.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
declare module '*.html' {
const value: string;
export default value;
}
93 changes: 93 additions & 0 deletions src/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
<section id="page">
<div class="header">
<div class="star"></div>
WANTED!
</div>

<div class="atrocity-crimes">
For the following atrocity crimes:
<div id="atrocity-crimes"></div>
</div>

<img id="face" />

<div class="wanted-name">
😈
<div id="wanted-name"></div>
😈
</div>

<div class="alive">PREFERABLE ALIVE WITH ALL OF LIMBS</div>

<div class="reward">
Reward from friends:
<div id="reward"></div>
</div>
</section>

<style>
#page {
display: block;
width: 190mm;
height: 270mm;
font-family: Kalam;
overflow: hidden;
}

.header {
font-family: 'Patua One';
font-size: 36pt;
text-align: center;
font-weight: 800;
line-height: 2;
border-top: 2pt solid;
border-bottom: 2pt solid;
margin-top: 15pt;
}

.star {
width: 50pt;
line-height: 30pt;
background: white;
margin: 0 auto;
margin-top: -20pt;
}

.atrocity-crimes {
font-size: 18pt;
text-align: center;
padding: 10pt;
}

#face {
width: 100%;
height: auto;
}

.wanted-name {
display: flex;
justify-content: center;
font-size: 28pt;
padding: 10pt;
}

#wanted-name {
padding: 0 10pt;
}

.alive {
font-family: 'Patua One';
font-size: 26pt;
font-weight: 600;
text-align: center;
line-height: 2;
border-top: 2pt solid;
border-bottom: 2pt solid;
}

.reward {
font-size: 18pt;
text-align: center;
padding: 10pt;
}
</style>
86 changes: 83 additions & 3 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,92 @@
import html from './index.html';
import { FontLoader } from './font-loader';

export class WantedPrintForm extends HTMLElement {
public readyEventName = 'form-ready';
public errorEventName = 'form-error';

public wantedName!: string;
public image!: File;
public atrocityCrimes!: string;
public reward!: string;

private componentShadowRoot!: ShadowRoot;

constructor() {
super();

const componentShadowRoot = this.attachShadow({ mode: 'open' });
this.componentShadowRoot = this.attachShadow({ mode: 'open' });

const template = document.createElement('template');
template.innerHTML = 'Wanted serious offender!';
componentShadowRoot.appendChild(template.content.cloneNode(true));
template.innerHTML = html;
this.componentShadowRoot.appendChild(template.content.cloneNode(true));
}

public async connectedCallback(): Promise<void> {
try {
if (!this.isValidInputs()) {
throw new Error('Invalid input values!');
}

const atrocityCrimes = this.componentShadowRoot.getElementById('atrocity-crimes');
atrocityCrimes && (atrocityCrimes.innerText = this.atrocityCrimes);

const imageSrc = URL.createObjectURL(this.image);
const face = this.componentShadowRoot.getElementById('face') as HTMLImageElement | null;

face && (await this.loadImage(imageSrc, face));

const wantedName = this.componentShadowRoot.getElementById('wanted-name');
wantedName && (wantedName.innerText = this.wantedName);

const reward = this.componentShadowRoot.getElementById('reward');
reward && (reward.innerText = this.reward);

const allText: string[] = this.getAllText();
await FontLoader.loadAllFonts(['Patua One', 'Kalam'], allText);

if (!this.isValidPaperSize()) {
throw new Error('Too match paper size!');
}
} catch (error) {
const errorEvent = new CustomEvent(this.errorEventName, { detail: error });
this.dispatchEvent(errorEvent);
return;
}

const readyEvent = new CustomEvent(this.readyEventName);
this.dispatchEvent(readyEvent);
}

private loadImage(src: string, img: HTMLImageElement): Promise<HTMLImageElement> {
return new Promise((resolve, reject) => {
img.addEventListener('load', () => resolve(img));
img.addEventListener('error', (err) => reject(err));
img.src = src;
});
}

private isValidInputs(): boolean {
return Boolean(this.atrocityCrimes && this.wantedName && this.image && this.reward);
}

private getAllText(): string[] {
return [
'WANTED!',
'For the following atrocity crimes:',
'Reward from friends:',
this.wantedName,
this.atrocityCrimes,
this.reward,
'😈',
'⍟',
'PREFERABLE ALIVE WITH ALL OF LIMBS',
];
}

private isValidPaperSize(): boolean {
const page = this.componentShadowRoot.getElementById('page');
return Boolean(page && page.offsetHeight === page.scrollHeight);
}
}

Expand Down
3 changes: 2 additions & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"strict": true,
"esModuleInterop": true,
"outDir": "./dist/",
"noImplicitAny": true
"noImplicitAny": true,
"types": ["css-font-loading-module"]
}
}
28 changes: 27 additions & 1 deletion webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,19 @@ const path = require('path');

const prodConfig = {
entry: './src/index.ts',
module: {
rules: [
{
test: /\.ts$/,
use: 'ts-loader',
exclude: /node_modules/,
},
{
test: /\.html$/i,
loader: 'html-loader',
},
],
},
resolve: {
extensions: ['.ts'],
},
Expand All @@ -15,8 +28,21 @@ const prodConfig = {

const devConfig = {
entry: './src/index.ts',
module: {
rules: [
{
test: /\.ts$/,
use: 'ts-loader',
exclude: /node_modules/,
},
{
test: /\.html$/i,
loader: 'html-loader',
},
],
},
resolve: {
extensions: ['.ts', '.js'],
extensions: ['.ts', '.js', '.html'],
modules: ['src', 'node_modules'],
},
output: {
Expand Down

0 comments on commit 4d871b1

Please sign in to comment.