Skip to content

Commit

Permalink
Add dummy search
Browse files Browse the repository at this point in the history
  • Loading branch information
mrkosima committed May 12, 2019
1 parent a0631ea commit 90aa18f
Show file tree
Hide file tree
Showing 23 changed files with 1,179 additions and 59 deletions.
1 change: 1 addition & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
webpack*
26 changes: 26 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
module.exports = {
extends: "eslint:recommended",
env: {
browser: true,
es6: true
},
parserOptions: {
ecmaVersion: 6,
sourceType: "module"
},
parser: "@typescript-eslint/parser",
plugins: ["@typescript-eslint", "lit"],
rules: {
"no-cond-assign": "off",
"no-console": "off",
"no-unused-vars": "off",
"no-debugger": "off",
// todo - fix warn/off rules
"lit/no-duplicate-template-bindings": "error",
"lit/no-template-bind": "error",
"lit/no-template-map": "warn",
"lit/no-useless-template-literals": "error",
"lit/attribute-value-entities": "error",
"lit/no-invalid-html": "error"
}
};
20 changes: 15 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,17 @@
# Book Search
# Books Search

Playground with:
- Custom Elements
Under the hood:
- TypeScript
- Speech Recongition
- [Open Library API](https://openlibrary.org/developers/api)
- [lit-html](https://github.com/polymer/lit-html)
- [lit-elements](https://github.com/polymer/lit-element)
- [Speech Recongition API](https://developer.mozilla.org/en-US/docs/Web/API/SpeechRecognition)
- [Open Library API](https://openlibrary.org/developers/api)


Useful resources:
- [Custom Elements v1: Reusable Web Components](https://developers.google.com/web/fundamentals/web-components/customelements)
- [Using the Web Speech API
](https://developer.mozilla.org/en-US/docs/Web/API/Web_Speech_API/Using_the_Web_Speech_API)
- [A curated list of awesome lit-html resources](https://github.com/web-padawan/awesome-lit-html)
- [How lit-html works?](https://github.com/Polymer/lit-html/wiki/How-it-Works)
- [Open Web Component Recommendations (open-wc)](https://open-wc.org/developing/)
10 changes: 10 additions & 0 deletions global.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
declare global {
var SpeechRecognitionDeclaration: {
new (): SpeechRecognition;
};

interface Window {
webkitSpeechRecognition: typeof SpeechRecognitionDeclaration;
}
}
export {};
23 changes: 17 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,25 +1,36 @@
{
"name": "book-search",
"name": "books-search",
"version": "0.0.1",
"main": "index.js",
"scripts": {
"start": "webpack-dev-server",
"build": "webpack"
"start": "webpack-dev-server --config webpack.dev.js",
"lint": "eslint",
"build": "webpack --config webpack.prod.js"
},
"repository": {
"type": "git",
"url": "git+ssh://git@github.com/mrkosima/book-search.git"
"url": "git+ssh://git@github.com/mrkosima/books-search.git"
},
"author": "Kanstantsin Klimashevich <klimashevich.k@gmail.com>",
"license": "UNLICENSED",
"homepage": "https://github.com/mrkosima/book-search#readme",
"homepage": "https://github.com/mrkosima/books-search#readme",
"devDependencies": {
"@typescript-eslint/eslint-plugin": "^1.8.0",
"@typescript-eslint/parser": "^1.8.0",
"clean-webpack-plugin": "^2.0.2",
"eslint": "^5.16.0",
"eslint-loader": "^2.1.2",
"eslint-plugin-lit": "^1.0.0",
"html-webpack-plugin": "^3.2.0",
"ts-loader": "^6.0.0",
"typescript": "^3.4.5",
"webpack": "^4.31.0",
"webpack-cli": "^3.3.2",
"webpack-dev-server": "^3.3.1"
"webpack-dev-server": "^3.3.1",
"webpack-merge": "^4.2.1"
},
"dependencies": {
"lit-element": "^2.1.0",
"timeago.js": "^4.0.0-beta.2"
}
}
Binary file added public/favicon.ico
Binary file not shown.
14 changes: 14 additions & 0 deletions public/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title><%= htmlWebpackPlugin.options.title %></title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
</head>

<body>
<chrome-checker minVersion="73">
<books-search></books-search>
</chrome-checker>
</body>
</html>
63 changes: 63 additions & 0 deletions src/api/booksService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
interface BookApi {
title: string;
isbn: string[];
author_name: string[];
}

interface BooksApi {
docs: BookApi[];
numFound: number;
start: number;
}

type CoverUrils = { [key in "small" | "medium" | "large"]: string };

export interface Book {
id: string;
authorName: string;
coverUrls: CoverUrils;
}

export type Books = Book[];

const SEARCH_API = "http://openlibrary.org/search.json";

const prepareQueryString = (text: string) =>
text
.replace(/[^\w\s]/g, "")
.split(" ")
.filter(word => !!word)
.join("+");

const fetchApi = <T>(path: string): Promise<T> =>
fetch(path).then(res => res.json());

const getCoverUrl = (
key: "isbn" | "oclc" | "lccn" | "olid" | "id",
value: string,
size: "S" | "M" | "L"
) => `http://covers.openlibrary.org/a/${key}/${value}-${size}.jpg`;

const getCoverUrls = (isbn: string): CoverUrils => ({
small: getCoverUrl("isbn", isbn, "S"),
medium: getCoverUrl("isbn", isbn, "M"),
large: getCoverUrl("isbn", isbn, "L")
});

const convertBooksApi = (booksApi: BooksApi): Books => {
return booksApi.docs
.filter(book => book.author_name && book.isbn && book.isbn.length > 0)
.map(book => ({
id: book.isbn[0],
authorName: book.author_name.join(", "),
coverUrls: getCoverUrls(book.isbn[0])
}));
};

export const searchBooks = (text: string): Promise<Books> =>
fetchApi<BooksApi>(`${SEARCH_API}?q=${prepareQueryString(text)}`).then(
books => convertBooksApi(books).slice(0, 5)
);

// http://covers.openlibrary.org/a/$key/$value-$size.jpg
// http://covers.openlibrary.org/a/isbn/$value-$size.jpg
1 change: 0 additions & 1 deletion src/app/index.ts

This file was deleted.

62 changes: 62 additions & 0 deletions src/components/books-gallery.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { LitElement, html, customElement, property, css } from "lit-element";
import { repeat } from "lit-html/directives/repeat";
import { Books } from "../api/booksService";

@customElement("books-gallery")
export class BooksGallery extends LitElement {
@property({ type: Array }) books: Books = [];

protected active: boolean = false;

static get styles() {
return css`
* {
color: red;
}
`;
}

static get properties() {
return {
active: { attribute: false, type: Boolean }
};
}

public connectedCallback() {
super.connectedCallback();
document.addEventListener("visibilitychange", this.checkActive);
window.addEventListener("focus", this.checkActive);
window.addEventListener("blur", this.checkActive);
this.checkActive();
}
public disconnectedCallback() {
document.removeEventListener("visibilitychange", this.checkActive);
window.removeEventListener("focus", this.checkActive);
window.removeEventListener("blur", this.checkActive);
super.disconnectedCallback();
}

protected render() {
const booksList = html`
<ul>
${repeat(
this.books,
book => book.id,
(book, i) => html`
<li>${book.authorName}</li>
`
)}
<ul></ul>
</ul>
`;
return html`
<h3>Books Gallery</h3>
${booksList}
<h4>Active: ${this.active.toString()}</h4>
`;
}

private checkActive = () => {
this.active = !document.hidden; // Todo - uncomment && document.hasFocus()
};
}
82 changes: 82 additions & 0 deletions src/components/books-search.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import { customElement, LitElement, css, html } from "lit-element";
import { searchBooks, Books } from "../api/booksService";

import "./voice-input";
import "./books-gallery";
import "./custom-loader";
import "./time-ago";

@customElement("books-search")
export class BooksSearch extends LitElement {
private books: Books = [];

private searchText: string = "";
private loading: boolean = false;
private updateTime: number;

static get styles() {
return css`
* {
color: green;
}
`;
}

static get properties() {
return {
books: { attribute: false, type: Array },
searchText: { attribute: false, type: String },
loading: { attribute: false, type: Boolean, reflect: true },
updateTime: { attribute: false, type: Number }
};
}

protected render() {
const content = this.loading
? html`
<custom-loader></custom-loader>
`
: html`
<books-gallery .books=${this.books}></books-gallery>
`;

return html`
<h1>Hello</h1>
<voice-input
placeholder="Search"
language="en"
@valueChanged=${this.onSearchChanged}
></voice-input>
<time-ago .time=${this.updateTime}></time-ago>
${content}
`;
}

private updateSearchText = (value: string) => {
if (this.searchText !== value) {
this.searchText = value;
this.fetchData(this.searchText);
}
};

private onSearchChanged = (event: CustomEvent) => {
this.updateSearchText(event.detail);
};

private fetchData = (searchText: string) => {
this.loading = true;
searchBooks(searchText).then(this.handleBooksLoaded, this.handleError);
};

private handleBooksLoaded = (books: Books) => {
this.loading = false;
this.books = books;
this.updateTime = Date.now();
};

private handleError = (error: any) => {
console.warn(error);
this.loading = false;
this.updateTime = null;
};
}
62 changes: 62 additions & 0 deletions src/components/chrome-checker.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import {
LitElement,
html,
customElement,
property,
TemplateResult
} from "lit-element";

@customElement("chrome-checker")
export class ChromeChecker extends LitElement {
@property({ type: String }) minVersion: string;

render() {
if (!this.supported()) {
let message: TemplateResult;
if (this.isChrome() && this.minVersion) {
message = html`
<p>
Please upgrade your Google Chrome browser to version
${this.minVersion} or higher.
</p>
`;
} else {
message = html`
<p>
To enter the website please use
<a href="https://www.google.com/chrome/">Google Chrome</a>.
</p>
`;
}

return html`
<h2>
Your browser is not supported
</h2>
${message}
`;
}
return html`
<slot></slot>
`;
}

private isChrome = () =>
/Chrome/.test(navigator.userAgent) && /Google Inc/.test(navigator.vendor);

private getChromeVersion = (): number => {
const match = /Chrome\/(\d+)/g.exec(navigator.userAgent);
if (match.length > 1) {
return +match[1];
}
return -1;
};

private supported = () => {
const chrome = this.isChrome();
if (chrome && !isNaN(+this.minVersion)) {
return +this.minVersion <= this.getChromeVersion();
}
return chrome;
};
}
Loading

0 comments on commit 90aa18f

Please sign in to comment.