Dwayne Harris 4 years ago
parent
commit
ce1d13c444
  1. 1675
      package-lock.json
  2. 7
      package.json
  3. 21
      postcss-preset-env.d.ts
  4. 79
      src/apps/gif-app/composer/app.tsx
  5. 12
      src/apps/gif-app/composer/gif.tsx
  6. 18
      src/apps/gif-app/composer/webpack.config.ts
  7. 93
      src/apps/text-app/composer/app.tsx
  8. 18
      src/apps/text-app/composer/webpack.config.ts
  9. 32
      src/communicator/index.ts
  10. 72
      src/styles/default.css
  11. 50
      src/styles/default.scss
  12. 18
      src/styles/spinner.css
  13. 18
      src/types/index.ts
  14. 3
      src/utils/index.ts

1675
package-lock.json
File diff suppressed because it is too large
View File

7
package.json

@ -11,22 +11,18 @@
"build": "run-s build:server build:text-app build:gif-app"
},
"devDependencies": {
"@types/classnames": "^2.2.9",
"@types/html-webpack-plugin": "^3.2.1",
"@types/mini-css-extract-plugin": "^0.8.0",
"@types/react": "^16.9.9",
"@types/react-dom": "^16.9.2",
"@types/request": "^2.48.3",
"@types/webpack": "^4.39.5",
"bulma": "^0.8.0",
"css-loader": "^3.2.0",
"file-loader": "^4.2.0",
"html-webpack-plugin": "^3.2.0",
"mini-css-extract-plugin": "^0.8.0",
"node-sass": "^4.12.0",
"npm-run-all": "^4.1.5",
"pino-pretty": "^3.2.2",
"sass-loader": "^8.0.0",
"style-loader": "^1.0.0",
"ts-loader": "^6.2.0",
"ts-node": "^8.4.1",
@ -38,10 +34,11 @@
"@fortawesome/fontawesome-svg-core": "^1.2.25",
"@fortawesome/free-solid-svg-icons": "^5.11.2",
"@fortawesome/react-fontawesome": "^0.1.7",
"classnames": "^2.2.6",
"dotenv": "^8.2.0",
"fastify": "^2.10.0",
"fastify-static": "^2.5.0",
"postcss-loader": "^3.0.0",
"postcss-preset-env": "^6.7.0",
"react": "^16.10.2",
"react-dom": "^16.10.2",
"request": "^2.88.0"

21
postcss-preset-env.d.ts

@ -0,0 +1,21 @@
declare module 'postcss-preset-env' {
import {
plugin, Plugin, ParserInput,
Result, LazyResult, Root, ProcessOptions
} from 'postcss';
interface PluginOptions {
stage?: number;
features?: any;
browsers?: string;
insertBefore?: any;
insertAfter?: any;
autoprefixer?: any;
preserve?: boolean;
importFrom?: string;
exportTo?: string;
}
const PostcssPresetEnv: Plugin<PluginOptions>;
export default PostcssPresetEnv;
}

79
src/apps/gif-app/composer/app.tsx

@ -1,13 +1,13 @@
import React, { FC, useState, useEffect } from 'react'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { faSearch } from '@fortawesome/free-solid-svg-icons'
import { Communicator } from '../../../communicator'
import classNames from 'classnames'
import { Communicator, Theme } from '../../../communicator'
import Gif from './gif'
import { ClassDictionary, GiphyGif } from '../../../types'
import { GiphyGif } from '../../../types'
const giphyIcon = require('./giphy.png')
import '../../../styles/default.scss'
import '../../../styles/default.css'
import '../../../styles/spinner.css'
type APIResponse = GiphyGif[]
@ -39,30 +39,30 @@ const App: FC<Props> = ({ communicator }) => {
const [gifs, setGifs] = useState<GiphyGif[]>([])
const [selected, setSelected] = useState('')
const buttonStyle: ClassDictionary = {
'button': true,
'is-primary': true,
'is-loading': posting,
}
const controlStyle: ClassDictionary = {
'control': true,
'has-icons-left': true,
'is-loading': searching,
}
const [theme, setTheme] = useState<Theme>({
primary: '#333',
primaryAlternate: '#fff',
secondary: '#777',
backgroundPrimary: '#fff',
backgroundSecondary: '#ccc',
text: '#555',
red: '#ff1a1a',
green: '#00802b',
blue: '#005ce6',
})
useEffect(() => {
const init = async () => {
try {
const content = await communicator.init()
if (content!.theme) setTheme(content!.theme)
if (content && content.parent && content.parent.data && content.parent.data.search) {
setSearch(content.parent.data.search)
}
await communicator.setHeight(1000)
} catch (err) {
console.error(err)
}
} catch (err) {}
}
init()
@ -73,9 +73,7 @@ const App: FC<Props> = ({ communicator }) => {
try {
const response = await fetch('/api/gifs/home')
setGifs(await response.json() as APIResponse)
} catch (err) {
console.error(err)
}
} catch (err) {}
}
const doSearch = async () => {
@ -83,9 +81,7 @@ const App: FC<Props> = ({ communicator }) => {
setSearching(true)
const response = await fetch(`/api/gifs/search?q=${search}`)
setGifs(await response.json() as APIResponse)
} catch (err) {
console.error(err)
}
} catch (err) {}
setSearching(false)
}
@ -129,31 +125,36 @@ const App: FC<Props> = ({ communicator }) => {
}
return (
<div>
<div style={{ backgroundColor: theme.backgroundPrimary }}>
<div className="field">
<p className={classNames(controlStyle)}>
<input className="input" type="text" placeholder="Search" value={search} onChange={(e) => handleSearchChange(e.target.value)} />
<span className="icon is-small is-left">
<div className="control-container">
<div className="icon" style={{ backgroundColor: theme.primary, color: theme.primaryAlternate }}>
<FontAwesomeIcon icon={faSearch} />
</span>
</p>
</div>
<div className="control">
<input
style={{ backgroundColor: theme.backgroundSecondary, borderColor: theme.secondary, color: theme.text }}
type="text"
placeholder="Search"
value={search}
onChange={(e) => handleSearchChange(e.target.value)} />
</div>
</div>
</div>
<div className="gifs is-flex">
<div className="gifs">
{gifs.map(gif => <Gif key={gif.id} gif={gif} selected={gif.id === selected} onSelect={setSelected} />)}
</div>
<nav className="level">
<div className="level-left">
<div className="level-item">
<img src={giphyIcon} />
</div>
<div>
<img src={giphyIcon} style={{ width: 50, height: 18 }} />
</div>
<div className="level-right">
<div className="level-item">
<button className={classNames(buttonStyle)} onClick={() => post()} disabled={!selected}>Post</button>
</div>
<div>
<button style={{ backgroundColor: theme.primary, color: theme.primaryAlternate }} onClick={() => post()} disabled={posting || !selected}>
{posting ? <div className="spinner"></div> : <span>Post</span>}
</button>
</div>
</nav>
</div>

12
src/apps/gif-app/composer/gif.tsx

@ -1,6 +1,5 @@
import React, { FC, useState, useEffect } from 'react'
import classNames from 'classnames'
import { ClassDictionary, GiphyGif } from '../../../types'
import React, { FC } from 'react'
import { GiphyGif } from '../../../types'
interface Props {
gif: GiphyGif
@ -9,13 +8,8 @@ interface Props {
}
const Gif: FC<Props> = ({ gif, selected, onSelect }) => {
const classes: ClassDictionary = {
gif: true,
selected,
}
return (
<div className={classNames(classes)} onClick={() => onSelect(gif.id)}>
<div className="gif" style={{ borderColor: selected ? '#f00' : '#333' }} onClick={() => onSelect(gif.id)}>
<img src={gif.images.fixed_height.url} alt={gif.title} />
</div>
)

18
src/apps/gif-app/composer/webpack.config.ts

@ -2,6 +2,7 @@ import { resolve } from 'path'
import { Configuration } from 'webpack'
import HtmlWebpackPlugin from 'html-webpack-plugin'
import MiniCssExtractPlugin from 'mini-css-extract-plugin'
import postcssPresetEnv from 'postcss-preset-env'
const config: Configuration = {
mode: 'development',
@ -29,16 +30,25 @@ const config: Configuration = {
use: 'ts-loader',
},
{
test: /\.scss$/,
test: /\.css$/,
use: [
MiniCssExtractPlugin.loader,
'css-loader',
{
loader: 'sass-loader',
loader: 'css-loader',
options: {
sourceMap: true,
importLoaders: 1,
},
},
{
loader: 'postcss-loader',
options: {
plugins: [
postcssPresetEnv({
stage: 2,
}),
]
}
}
],
},
{

93
src/apps/text-app/composer/app.tsx

@ -1,11 +1,17 @@
import React, { FC, useState, useEffect } from 'react'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { faEyeSlash } from '@fortawesome/free-solid-svg-icons'
import { Communicator } from '../../../communicator'
import classNames from 'classnames'
import { ClassDictionary, Post } from '../../../types'
import { faShieldAlt } from '@fortawesome/free-solid-svg-icons'
import { Communicator, Post, Theme } from '../../../communicator'
import '../../../styles/default.scss'
import '../../../styles/default.css'
import '../../../styles/spinner.css'
const promts = [
"What's going on?",
'What it do?',
"What's happening?",
"How's your day going?"
]
interface Props {
communicator: Communicator
@ -18,19 +24,22 @@ const App: FC<Props> = ({ communicator }) => {
const [posting, setPosting] = useState(false)
const [parent, setParent] = useState<Post | null>(null)
const [theme, setTheme] = useState<Theme>({
primary: '#333',
primaryAlternate: '#fff',
secondary: '#777',
backgroundPrimary: '#fff',
backgroundSecondary: '#ccc',
text: '#555',
red: '#ff1a1a',
green: '#00802b',
blue: '#005ce6',
})
const charactersLeft = maxCharacters - content.length
const showCharactersLeft = charactersLeft < (maxCharacters / 2)
const buttonDisabled = charactersLeft < 1 || content.length === 0 || posting
const showCharactersStyle: ClassDictionary = {
'has-text-danger': charactersLeft < (maxCharacters * 0.1),
}
const buttonStyle: ClassDictionary = {
'button': true,
'is-primary': true,
'is-loading': posting,
}
const showCharactersDanger = charactersLeft < (maxCharacters * 0.1)
const buttonText = parent ? 'Reply!' : 'Post!'
@ -39,11 +48,10 @@ const App: FC<Props> = ({ communicator }) => {
try {
const response = await communicator.init()
if (response!.parent) setParent(response!.parent)
if (response!.theme) setTheme(response!.theme)
await communicator.setHeight(document.body.offsetHeight)
} catch (err) {
console.error('App Component: ', err)
}
} catch (err) {}
}
init()
@ -61,41 +69,46 @@ const App: FC<Props> = ({ communicator }) => {
})
setContent('')
setCover('')
} catch (err) {
console.error('App Component: ', err)
}
} catch (err) {}
setPosting(false)
}
return (
<div>
<div className="field">
<div className="control">
<textarea className="textarea" placeholder="What it do?" value={content} onChange={(e) => setContent(e.target.value)} />
</div>
<div style={{ backgroundColor: theme.backgroundPrimary }}>
<div className="control">
<textarea
style={{ backgroundColor: theme.backgroundSecondary, borderColor: theme.secondary, color: theme.text }}
placeholder="What it do?"
value={content}
onChange={(e) => setContent(e.target.value)} />
</div>
<div className="field">
<p className="control has-icons-left">
<input className="input" type="text" placeholder="Cover Text" value={cover} onChange={(e) => setCover(e.target.value)} />
<span className="icon is-small is-left">
<FontAwesomeIcon icon={faEyeSlash} />
</span>
</p>
<div className="control-container">
<div className="icon" style={{ backgroundColor: theme.primary, color: theme.primaryAlternate }}>
<FontAwesomeIcon icon={faShieldAlt} />
</div>
<div className="control">
<input
style={{ backgroundColor: theme.backgroundSecondary, borderColor: theme.secondary, color: theme.text }}
type="text"
placeholder="Cover Text"
value={cover}
onChange={(e) => setCover(e.target.value)} />
</div>
</div>
</div>
<nav className="level">
<div className="level-left">
<div className="level-item">
{showCharactersLeft && <p className={classNames(showCharactersStyle)}>{charactersLeft} Chars Left</p>}
</div>
<div>
{showCharactersLeft ? <p style={{ color: showCharactersDanger ? theme.red : theme.text }}>{charactersLeft} Chars Left</p> : <p>&nbsp;</p>}
</div>
<div className="level-right">
<div className="level-item">
<button className={classNames(buttonStyle)} onClick={() => post()} disabled={buttonDisabled}>{buttonText}</button>
</div>
<div>
<button style={{ backgroundColor: theme.primary, color: theme.primaryAlternate }} onClick={() => post()} disabled={buttonDisabled}>
{posting ? <div className="spinner"></div> : <span>{buttonText}</span>}
</button>
</div>
</nav>
</div>

18
src/apps/text-app/composer/webpack.config.ts

@ -2,6 +2,7 @@ import { resolve } from 'path'
import { Configuration } from 'webpack'
import HtmlWebpackPlugin from 'html-webpack-plugin'
import MiniCssExtractPlugin from 'mini-css-extract-plugin'
import postcssPresetEnv from 'postcss-preset-env'
const config: Configuration = {
mode: 'development',
@ -29,16 +30,25 @@ const config: Configuration = {
use: 'ts-loader',
},
{
test: /\.scss$/,
test: /\.css$/,
use: [
MiniCssExtractPlugin.loader,
'css-loader',
{
loader: 'sass-loader',
loader: 'css-loader',
options: {
sourceMap: true,
importLoaders: 1,
},
},
{
loader: 'postcss-loader',
options: {
plugins: [
postcssPresetEnv({
stage: 2,
}),
]
}
}
],
},
],

32
src/communicator/index.ts

@ -1,8 +1,38 @@
import { Post } from '../types'
export interface Attachment {
url: string
text?: string
cover?: string
}
export interface PostData {
[key: string]: any
}
export interface Post {
text?: string
cover?: string
attachments?: Attachment[]
data?: PostData
visible: boolean
}
export interface Theme {
primary: string
primaryAlternate: string
secondary: string
backgroundPrimary: string
backgroundSecondary: string
text: string
red: string
green: string
blue: string
}
export interface MessageContent {
[key: string]: any
height?: number
theme?: Theme
colorScheme?: string
parent?: Post
}

72
src/styles/default.css

@ -0,0 +1,72 @@
@charset "utf-8";
@import url('https://fonts.googleapis.com/css?family=Source+Sans+Pro&display=swap');
:root {
--default-font: 'Source Sans Pro', sans-serif;
--input-padding: 0.5rem;
}
html {
font-size: 18px;
}
body {
font-family: var(--default-font);
margin: 0px;
padding: 10px;
}
input, textarea, select {
border: 1px solid;
box-sizing: border-box;
font-family: var(--default-font);
font-size: 1rem;
padding: var(--input-padding);
width: 100%;
}
button {
border: none;
border-radius: 8px;
cursor: pointer;
font-size: 0.8rem;
font-weight: 700;
padding: 0.5rem 1rem;
min-width: 100px;
}
nav.level {
display: flex;
justify-content: space-between;
}
div.control-container {
display: flex;
padding: 0.5rem 0px;
}
div.control-container > div.icon {
margin: 0px;
padding: var(--input-padding);
}
div.control {
flex-grow: 1;
}
div.gifs {
display: flex;
flex-wrap: wrap;
}
div.gif {
margin: 5px;
}
div.gif > img {
height: 100px;
}
.gif.selected {
border: 3px solid;
}

50
src/styles/default.scss

@ -1,50 +0,0 @@
@charset "utf-8";
@import url('https://fonts.googleapis.com/css?family=Open+Sans:400,400i,700&display=swap');
// Colors
$orange: hsl(14, 100%, 53%);
$yellow: hsl(48, 100%, 67%);
$green: hsl(141, 65%, 31%);
$turquoise: hsl(171, 100%, 41%);
$cyan: hsl(204, 86%, 53%);
$blue: hsl(217, 72%, 30%);
$purple: hsl(271, 63%, 32%);
$red: hsl(348, 71%, 42%);
$grey: hsl(0, 0%, 48%);
$grey-light: hsl(0, 0%, 71%);
$grey-lighter: hsl(0, 0%, 86%);
$white-ter: hsl(0, 0%, 96%);
$white-bis: hsl(0, 0%, 98%);
$family-sans-serif: "Open Sans", sans-serif;
$primary: $blue;
$body-size: 14px;
@import "../../node_modules/bulma/sass/utilities/_all.sass";
@import "../../node_modules/bulma/sass/base/_all.sass";
@import "../../node_modules/bulma/sass/form/_all.sass";
@import "../../node_modules/bulma/sass/elements/button.sass";
@import "../../node_modules/bulma/sass/elements/icon.sass";
@import "../../node_modules/bulma/sass/components/level.sass";
body {
padding: 10px;
}
div.gifs {
display: flex;
flex-wrap: wrap;
}
div.gif {
margin: 5px;
img {
height: 100px;
}
}
.gif.selected {
border: solid 3px $green;
}

18
src/styles/spinner.css

@ -0,0 +1,18 @@
.spinner {
width: 40px;
height: 40px;
background-color: #333;
margin: 100px auto;
animation: sk-rotateplane 1.2s infinite ease-in-out;
}
@keyframes sk-rotateplane {
0% {
transform: perspective(120px) rotateX(0deg) rotateY(0deg);
} 50% {
transform: perspective(120px) rotateX(-180.1deg) rotateY(0deg);
} 100% {
transform: perspective(120px) rotateX(-180deg) rotateY(-179.9deg);
}
}

18
src/types/index.ts

@ -6,24 +6,6 @@ export interface PluginOptions {
}
export interface Attachment {
url: string
text?: string
cover?: string
}
export interface PostData {
[key: string]: any
}
export interface Post {
text?: string
cover?: string
attachments?: Attachment[]
data?: PostData
visible: boolean
}
export interface GiphyGif {
type: string
id: string

3
src/utils/index.ts

@ -0,0 +1,3 @@
import { ClassDictionary } from '../types'
export const classNames = (dictionary: ClassDictionary) => Object.entries(dictionary).filter(([_, value]) => !!value).map(([key, _]) => key).join(' ')
Loading…
Cancel
Save