From e93f095be1ea22ddaa4a41edc4f0426e7f9b5a90 Mon Sep 17 00:00:00 2001 From: Markus Tacker Date: Tue, 30 Jul 2019 11:30:47 +0200 Subject: [PATCH] feat: allow to name cats --- src/Avatar/updateAvatar.ts | 40 ------------ src/Cat/Cat.scss | 4 +- src/Cat/Cat.tsx | 103 ++++++++++++++++++------------- src/Cat/updateThingAttributes.ts | 19 ++++++ src/Cat/uploadAvatar.ts | 24 +++++++ src/Cats/List.tsx | 18 +++--- src/Editable/Editable.scss | 29 +++++++++ src/Editable/Editable.tsx | 48 ++++++++++++++ 8 files changed, 190 insertions(+), 95 deletions(-) delete mode 100644 src/Avatar/updateAvatar.ts create mode 100644 src/Cat/updateThingAttributes.ts create mode 100644 src/Cat/uploadAvatar.ts create mode 100644 src/Editable/Editable.scss create mode 100644 src/Editable/Editable.tsx diff --git a/src/Avatar/updateAvatar.ts b/src/Avatar/updateAvatar.ts deleted file mode 100644 index 689d9000..00000000 --- a/src/Avatar/updateAvatar.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { Iot, S3 } from 'aws-sdk' -import { v4 } from 'uuid' - -export const updateAvatar = ({ - s3, - iot, - bucketName, - thingName, -}: { - s3: S3 - iot: Iot - bucketName: string - thingName: string -}) => async ({ blob }: { blob: Blob }): Promise<{ url: string }> => { - const id = v4() - const url = `https://${bucketName}.s3.amazonaws.com/${id}.jpg` - await s3 - .putObject({ - Bucket: `${bucketName}`, - Key: `${id}.jpg`, - ContentType: blob.type, - ContentLength: blob.size, - Body: blob, - }) - .promise() - - await iot - .updateThing({ - thingName, - attributePayload: { - attributes: { - avatar: url, - }, - merge: true, - }, - }) - .promise() - - return { url } -} diff --git a/src/Cat/Cat.scss b/src/Cat/Cat.scss index a55252f1..3722b0c8 100644 --- a/src/Cat/Cat.scss +++ b/src/Cat/Cat.scss @@ -11,10 +11,10 @@ top: 0; left: 50%; margin-left: -38.5px; - margin-top: -38.5px; + margin-top: -53.5px; z-index: 9000; } h2 { - margin-top: 25px; + margin: 10px; } } \ No newline at end of file diff --git a/src/Cat/Cat.tsx b/src/Cat/Cat.tsx index dcddc399..c9343a90 100644 --- a/src/Cat/Cat.tsx +++ b/src/Cat/Cat.tsx @@ -11,19 +11,23 @@ import { WarningRounded as WarningIcon } from '@material-ui/icons' import './Cat.scss' import { AvatarPicker } from '../Avatar/AvatarPicker' -import { updateAvatar } from '../Avatar/updateAvatar' +import { uploadAvatar } from './uploadAvatar' +import { Editable } from '../Editable/Editable' +import { updateThingAttributes } from './updateThingAttributes' const ShowCat = ({ catId, iot, iotData, - avatarUploader, + onAvatarChange, + onNameChange, identityId, credentials, }: { iot: Iot iotData: IotData - avatarUploader: (args: { blob: Blob }) => Promise<{ url: string }> + onAvatarChange: (args: { avatar: Blob }) => void + onNameChange: (args: { name: string }) => void catId: string identityId: string credentials: { @@ -36,6 +40,7 @@ const ShowCat = ({ const [cat, setCat] = useState({ name: catId, avatar: 'https://placekitten.com/75/75', + version: 0 }) const [reported, setReported] = useState({} as { [key: string]: { v: any; ts: string } @@ -91,16 +96,17 @@ const ShowCat = ({ }), ]) - .then(([{ thingName, attributes }, connection, reported]) => { + .then(([{ thingName, attributes, version }, _, reported]) => { setLoading(false) console.log('Inital reported state', catId, reported) setReported(reported) if (thingName) { setCat({ - name: thingName, + name: (attributes && attributes.name) || thingName, avatar: (attributes && attributes.avatar) || 'https://placekitten.com/75/75', + version: version || 0, }) } }) @@ -129,8 +135,9 @@ const ShowCat = ({ { - // Display image directly + // Display image directly const reader = new FileReader() reader.onload = (e: any) => { setCat({ @@ -139,24 +146,20 @@ const ShowCat = ({ }) } reader.readAsDataURL(blob) - - // Upload - avatarUploader({ blob }) - .then(({ url }) => { - setCat({ - ...cat, - avatar: url, - }) - }) - .catch(error => { - console.error(error) - }) + onAvatarChange({ avatar: blob }) }} > {cat.name} - -

{cat.name}

+

+ { + onNameChange({ name: v }) + }} + /> +

@@ -192,34 +195,50 @@ const ShowCat = ({ export const Cat = ({ catId }: { catId: string }) => ( - {credentials => { - const s3 = new S3({ - credentials, - region: process.env.REACT_APP_REGION, - }) - return ( - - {identityId => ( - - {({ iot, iotData }) => ( + {credentials => ( + + {identityId => ( + + {({ iot, iotData }) => { + const s3 = new S3({ + credentials, + region: process.env.REACT_APP_REGION, + }) + const avatarUploader = uploadAvatar({ + s3, + bucketName: `${process.env.REACT_APP_AVATAR_BUCKET_NAME}`, + }) + const attributeUpdater = updateThingAttributes({ + iot, + thingName: catId, + }) + return ( { + avatarUploader({ + avatar, + }) + .then(({ url }) => attributeUpdater({ avatar: url })) + .catch(err => { + console.error(err) + }) + }} + onNameChange={({ name }) => { + attributeUpdater({ name }).catch(err => { + console.error(err) + }) + }} /> - )} - - )} - - ) - }} + ) + }} + + )} + + )} ) diff --git a/src/Cat/updateThingAttributes.ts b/src/Cat/updateThingAttributes.ts new file mode 100644 index 00000000..97f5063b --- /dev/null +++ b/src/Cat/updateThingAttributes.ts @@ -0,0 +1,19 @@ +import { Iot } from 'aws-sdk' + +export const updateThingAttributes = ({ + iot, + thingName, +}: { + iot: Iot + thingName: string +}) => async (attributes: { [key: string]: string }): Promise => { + await iot + .updateThing({ + thingName, + attributePayload: { + attributes: attributes, + merge: true, + }, + }) + .promise() +} diff --git a/src/Cat/uploadAvatar.ts b/src/Cat/uploadAvatar.ts new file mode 100644 index 00000000..1b11a66b --- /dev/null +++ b/src/Cat/uploadAvatar.ts @@ -0,0 +1,24 @@ +import { S3 } from 'aws-sdk' +import { v4 } from 'uuid' + +export const uploadAvatar = ({ + s3, + bucketName, +}: { + s3: S3 + bucketName: string +}) => async ({ avatar }: { avatar: Blob }): Promise<{ url: string }> => { + const id = v4() + const url = `https://${bucketName}.s3.amazonaws.com/${id}.jpg` + await s3 + .putObject({ + Bucket: `${bucketName}`, + Key: `${id}.jpg`, + ContentType: avatar.type, + ContentLength: avatar.size, + Body: avatar, + }) + .promise() + + return { url } +} diff --git a/src/Cats/List.tsx b/src/Cats/List.tsx index 312bd693..d92aecc3 100644 --- a/src/Cats/List.tsx +++ b/src/Cats/List.tsx @@ -7,7 +7,7 @@ import { Error } from '../Error/Error' const ListCats = ({ iot }: { iot: Iot }) => { const [loading, setLoading] = useState(true) - const [cats, setCats] = useState([] as { name: string }[]) + const [cats, setCats] = useState([] as { id: string, name: string }[]) const [error, setError] = useState() useEffect(() => { iot @@ -15,8 +15,9 @@ const ListCats = ({ iot }: { iot: Iot }) => { .promise() .then(({ things }) => { setCats( - (things || []).map(({ thingName }) => ({ - name: thingName || 'unknown', + (things || []).map(({ thingName, attributes }) => ({ + id: thingName || 'unknown', + name: (attributes && attributes.name) || thingName || 'unknown', })), ) setLoading(false) @@ -32,16 +33,11 @@ const ListCats = ({ iot }: { iot: Iot }) => { Cats - - - - - - {cats.map(({ name }) => ( - + {cats.map(({ id, name }) => ( + ))} diff --git a/src/Editable/Editable.scss b/src/Editable/Editable.scss new file mode 100644 index 00000000..4fd84f58 --- /dev/null +++ b/src/Editable/Editable.scss @@ -0,0 +1,29 @@ +.editable { + width: 100%; + &.error { + animation: shake 0.82s cubic-bezier(.36,.07,.19,.97) both; + transform: translate3d(0, 0, 0); + backface-visibility: hidden; + perspective: 1000px; + border-color: red; + } +} + + +@keyframes shake { + 10%, 90% { + transform: translate3d(-1px, 0, 0); + } + + 20%, 80% { + transform: translate3d(2px, 0, 0); + } + + 30%, 50%, 70% { + transform: translate3d(-4px, 0, 0); + } + + 40%, 60% { + transform: translate3d(4px, 0, 0); + } +} \ No newline at end of file diff --git a/src/Editable/Editable.tsx b/src/Editable/Editable.tsx new file mode 100644 index 00000000..c803d3e5 --- /dev/null +++ b/src/Editable/Editable.tsx @@ -0,0 +1,48 @@ +import React, { useState } from 'react' +import classNames from 'classnames' + +import './Editable.scss' + +export const Editable = ({ + text: originalText, + onChange, +}: { + text: string + onChange: (updatedText: string) => void +}) => { + const [editing, setEditing] = useState(false) + const [input, updateInput] = useState(originalText) + const [text, updateText] = useState(originalText) + if (editing) { + return ( + { + if (!input.length) { + return + } + setEditing(false) + if (input !== text) { + updateText(input) + onChange(input) + } + }} + onChange={({ target: { value } }) => { + updateInput(value) + }} + /> + ) + } + return ( + { + setEditing(true) + }} + > + {text} + + ) +}
name
- {name} + {name}