即席でつくった
成果物
使用ライブラリ周り
- Firebase Storage
- Next.js
- Netlify Functions
ソース
package.json
{
"scripts": {
"dev": "next dev",
"build": "next build",
"postbuild": "next-on-netlify"
},
"devDependencies": {
"@types/node": "^14.14.7",
"@types/react": "^16.9.56",
"@types/react-dom": "^16.9.9",
"@types/react-router-dom": "^5.1.6",
"typescript": "^4.0.5"
},
"dependencies": {
"bootstrap": "^5.0.0-alpha1",
"firebase": "^8.0.2",
"next": "^10.0.1",
"next-on-netlify": "^2.6.1",
"popper.js": "^1.16.1",
"query-string": "^6.13.7",
"react": "^17.0.1",
"react-dom": "^17.0.1",
"react-router-dom": "^5.2.0"
}
}
next-env.d.ts
/// <reference types="next" />
/// <reference types="next/types/global" />
tsconfig.json
{
"compilerOptions": {
"jsx": "preserve",
"lib": ["dom", "es2019"],
"module": "esnext",
"moduleResolution": "node",
"target": "es5",
"allowJs": true,
"skipLibCheck": true,
"strict": false,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"esModuleInterop": true,
"resolveJsonModule": true,
"isolatedModules": true,
"baseUrl": "./",
"paths": {
"src/*": ["src/*"]
}
},
"exclude": ["node_modules"],
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"]
}
netlify.toml
[build]
command = "npm run build"
functions = "out_functions"
publish = "out_public"
src/pages/_app.tsx
import React from 'react';
import { AppProps } from 'next/app';
import 'bootstrap/dist/css/bootstrap.min.css';
const MyApp = ({ Component, pageProps }: AppProps) => {
React.useEffect(() => {
require('bootstrap/dist/js/bootstrap.js');
}, []);
return <Component {...pageProps} />;
};
export default MyApp;
src/pages/index.tsx
import React from 'react';
import firebase from 'src/firebase';
const queryString = require('query-string');
const imageDraw = (selector, path) => {
const d = document.getElementById(selector) as HTMLCanvasElement;
const ctx = d.getContext('2d') as CanvasRenderingContext2D;
const img = new Image();
img.src = path;
img.onload = function () {
ctx.drawImage(img, 0, 0, 50, 50);
};
};
const createImage = (context: any): Promise<any> => {
return new Promise(resolve => {
var image = new Image();
image.src = context.canvas.toDataURL();
image.onload = function () {
resolve(image);
};
});
};
const getRandomStr = () => {
const _s = 'abcdefghijklmnopqrstuvwxyz0123456789';
const _ret = [...Array(10)]
.map(() => _s[Math.floor(Math.random() * _s.length)])
.join('');
return _ret + new Date().getTime();
};
const Home = () => {
const [checkbox, setCheckbox] = React.useState([false, false, false]);
React.useEffect(() => {
(async () => {
imageDraw('image1', 'img/ibu.jpg');
imageDraw('image2', 'img/pika.jpg');
imageDraw('image3', 'img/pipi.jpg');
})();
}, []);
return (
<>
<h2>合成する画像を選択</h2>
<div className="form-check form-check-inline">
<input
className="form-check-input"
type="checkbox"
onChange={() => {
checkbox[0] = !checkbox[0];
setCheckbox(checkbox);
}}
/>
<canvas id="image1" width="50"></canvas>
</div>
<div className="form-check form-check-inline">
<input
className="form-check-input"
type="checkbox"
onChange={() => {
checkbox[1] = !checkbox[1];
setCheckbox(checkbox);
}}
/>
<canvas id="image2" width="50"></canvas>
</div>
<div className="form-check form-check-inline">
<input
className="form-check-input"
type="checkbox"
onChange={() => {
checkbox[2] = !checkbox[2];
setCheckbox(checkbox);
}}
/>
<canvas id="image3" width="50"></canvas>
</div>
<div className="form-group">
<button
type="button"
className="btn btn-primary"
onClick={async () => {
if (!checkbox[0] && !checkbox[1] && !checkbox[2]) {
alert('画像を選択してください');
return;
}
const d = document.getElementById('concat') as HTMLCanvasElement;
const ctx = d.getContext('2d') as CanvasRenderingContext2D;
for (let i = 0; i < checkbox.length; i++) {
if (!checkbox[i]) {
continue;
}
const _dd = document.getElementById(
'image' + (i + 1)
) as HTMLCanvasElement;
const _image = _dd.getContext('2d') as CanvasRenderingContext2D;
ctx.drawImage(await createImage(_image), i * 50, 0);
}
const image = new Image();
image.src = ctx.canvas.toDataURL();
// アップロード処理
const _file_name = getRandomStr() + '.png';
const ref = firebase.storage().ref().child(_file_name);
ref
.putString(image.src, 'data_url')
.then(() => {
ref
.getDownloadURL()
.then(function (url) {
const params: { token: string } = queryString.parse(url);
location.href = `/sub?q=${_file_name}&token=${params.token}`;
})
.catch(function (error) {});
})
.catch(error => {
console.log(error);
});
}}
>
作成
</button>
</div>
<br />
<br />
<canvas id="concat" width="150"></canvas>
</>
);
};
export default Home;
src/pages/sub.tsx
import React from 'react';
import firebase from 'src/firebase';
import Loading from 'src/components/loading';
const Sub = (p: { q: string; token: string }) => {
const [display, setDisplay] = React.useState(false);
React.useEffect(() => {
(async () => {
const ret = await firebase
.storage()
.ref()
.child(p.q)
.getDownloadURL()
.catch(error => {
location.href = '/';
});
setDisplay(true);
})();
}, []);
if (!display) {
return (
<Loading
url={`https://firebasestorage.googleapis.com/v0/b/fddfd-f7b92.appspot.com/o/${p.q}?alt=media&token=${p.token}`}
/>
);
}
let param = location.search;
const url = location.href.replace(param, '');
param = encodeURIComponent(param);
return (
<>
<a href="/">戻る</a>
<img
src={`https://firebasestorage.googleapis.com/v0/b/fddfd-f7b92.appspot.com/o/${p.q}?alt=media&token=${p.token}`}
width={150}
/>
<a href={`http://twitter.com/share?url=${url}${param}`} target="_blank">
ツイート
</a>
</>
);
};
export async function getServerSideProps({ query, res, req }) {
console.log(query);
if (query['q'] == undefined || query['token'] == undefined) {
res.writeHead(302, { Location: '/' });
res.end();
}
return {
props: {
q: query['q'],
token: query['token'],
},
};
}
export default Sub;
src/components/loading.tsx
import * as React from 'react';
import Head from 'next/head';
export default (params: { url: string }) => {
return (
<>
<Head>
<meta property="og:url" content="https://code.itsumen.com" />
<meta property="og:type" content="website" />
<meta property="og:site_name" content="サイト名" />
<meta property="og:image" content={params.url} />
<meta property="og:title" content="サイト名" />
</Head>
<div className="position-absolute h-100 w-100 m-0 d-flex align-items-center justify-content-center">
<div className="spinner-border text-primary" role="status">
<span className="sr-only">Loading...</span>
</div>
</div>
</>
);
};
firebase/index.ts
import firebase from 'firebase/app';
import 'firebase/storage';
const firebaseConfig = {
apiKey: 'AIzaSyB8olQXWPvTu9nehgWClS7PZzRLKUrqEtw',
authDomain: 'fddfd-f7b92.firebaseapp.com',
databaseURL: 'https://fddfd-f7b92.firebaseio.com',
projectId: 'fddfd-f7b92',
storageBucket: 'fddfd-f7b92.appspot.com',
messagingSenderId: '301190636009',
appId: '1:301190636009:web:3ee8471f31d4aa64fcab78',
measurementId: 'G-854P4MZ60D',
};
if (!firebase.apps.length) {
firebase.initializeApp(firebaseConfig);
}
export default firebase;
public/img/ibu.jpg
public/img/pika.jpg
public/img/pipi.jpg
Like!