add: dcd files
This commit is contained in:
parent
f847425a52
commit
13b5e68bf6
73
README.md
Normal file
73
README.md
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
## Dynamic Content Delivery
|
||||||
|
|
||||||
|
A static website that returns appropriate content to the website user based on the provided ID.
|
||||||
|
|
||||||
|
## Which files am I allowed to edit?
|
||||||
|
- [template section of index.html](index.html)
|
||||||
|
- all files in `data` directory:
|
||||||
|
- [constants.js](data/constants.js)
|
||||||
|
- [custom-style.css](data/custom-style.css)
|
||||||
|
|
||||||
|
## How to use
|
||||||
|
Firstly, create template for your webpage. Go to [index.html](index.html) and edit code **only between these lines**:
|
||||||
|
```html
|
||||||
|
<!-- TEMPLATE BEGINS -->
|
||||||
|
|
||||||
|
<!-- TEMPLATE ENDS -->
|
||||||
|
```
|
||||||
|
This is the **template section** of the HTML file.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
```html
|
||||||
|
<!-- TEMPLATE BEGINS -->
|
||||||
|
<p id="name">Hi!</p>
|
||||||
|
<!-- TEMPLATE ENDS -->
|
||||||
|
```
|
||||||
|
|
||||||
|
Then, prepare your API:
|
||||||
|
1. Clone [this](https://github.com/sadorowo/dynamic-content-delivery-api) repository.
|
||||||
|
2. Edit [data/data-source.json] file:
|
||||||
|
- provide your data in `DATA` constant like this:
|
||||||
|
```javascript
|
||||||
|
const DATA = {
|
||||||
|
"id1": {
|
||||||
|
"#name": {
|
||||||
|
"textContent": "John",
|
||||||
|
"style": {
|
||||||
|
"color": "red"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"id2": {
|
||||||
|
"#name": {
|
||||||
|
"textContent": "Anna",
|
||||||
|
"style": {
|
||||||
|
"color": "blue"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
- edit your [data/config.json]:
|
||||||
|
1. provide your API host in `API_HOST` constant.
|
||||||
|
2. provide your API port in `PORT` constant.
|
||||||
|
|
||||||
|
> Warning!
|
||||||
|
> Do not change the structure of the `DATA` object. It must be an object with keys being IDs and values, which are objects with CSS selectors as keys and objects with DOM properties as values.
|
||||||
|
> Make sure that your API is running on the same port as the one provided in `PORT` constant.
|
||||||
|
|
||||||
|
3. Deploy your API:
|
||||||
|
1. Install Node.js and npm.
|
||||||
|
2. Run `npm install` in the root directory of the API.
|
||||||
|
3. Run `npm start` in the root directory of the API.
|
||||||
|
|
||||||
|
## Custom styles
|
||||||
|
This website supports custom CSS. Just edit [this](data/custom-style.css) file.
|
||||||
|
If provided style doesn't work, try adding `!important` to the end of CSS rule.
|
||||||
|
|
||||||
|
## License
|
||||||
|
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
||||||
|
|
||||||
|
## Authors
|
||||||
|
- [**@sadorowo**](https://github.com/sadorowo) - original author
|
11
data/constants.js
Normal file
11
data/constants.js
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
const API_HOST = 'http://localhost';
|
||||||
|
const API_VERSION = 1;
|
||||||
|
const API_PORT = 3000;
|
||||||
|
|
||||||
|
const prepareUrl = (...urls) => `${API_HOST}:${API_PORT}/api/v${API_VERSION}/${urls.join('/')}`;
|
||||||
|
|
||||||
|
// Leave this unchanged, if you haven't modified anything in API route configuration.
|
||||||
|
const ROUTES = {
|
||||||
|
GET_RESOURCE: (id) => prepareUrl('resource', id),
|
||||||
|
STATUS: prepareUrl('status')
|
||||||
|
}
|
1
data/custom-style.css
Normal file
1
data/custom-style.css
Normal file
@ -0,0 +1 @@
|
|||||||
|
/* If rule statements doesn't work, add !important to the end of it. */
|
23
index.html
Normal file
23
index.html
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<title>Content Delivery Network</title>
|
||||||
|
<link rel="stylesheet" href="style.css">
|
||||||
|
<meta name="viewport" content="width=device-width,initial-scale=1"/>
|
||||||
|
<script defer src="data/constants.js"></script>
|
||||||
|
<script defer src="scripts/sdk.js"></script>
|
||||||
|
<script defer src="scripts/script.js"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="internal__start-page">
|
||||||
|
<h1>Enter your access code:</h1>
|
||||||
|
<input type="text"/>
|
||||||
|
<button id="internal__submit">Submit</button>
|
||||||
|
</div>
|
||||||
|
<div id="internal__template">
|
||||||
|
<!-- TEMPLATE BEGINS -->
|
||||||
|
|
||||||
|
<!-- TEMPLATE ENDS -->
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
54
scripts/script.js
Normal file
54
scripts/script.js
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
const startPage = document.getElementById('internal__start-page');
|
||||||
|
const templatePage = document.getElementById('internal__template');
|
||||||
|
|
||||||
|
const wait = (time) => new Promise(resolve => setTimeout(resolve, time * 1000));
|
||||||
|
|
||||||
|
// Check if API is alive
|
||||||
|
fetchStatus()
|
||||||
|
.catch(() => alert('API is down or an error occurred.'));
|
||||||
|
|
||||||
|
if (!startPage || !templatePage) {
|
||||||
|
alert("Base website template got corrupted! Can't find either start page or template page. Closing the window.");
|
||||||
|
window.close();
|
||||||
|
} else {
|
||||||
|
async function proceed() {
|
||||||
|
const input = document.querySelector('#internal__start-page > input');
|
||||||
|
const patches = await fetchResource(input.value);
|
||||||
|
|
||||||
|
if (!patches) return alert('Bad access code!');
|
||||||
|
applyPatches(patches);
|
||||||
|
await fadeBlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyPatches(patches) {
|
||||||
|
for (const [key, object] of Object.entries(patches)) {
|
||||||
|
const element = document.querySelector(key);
|
||||||
|
if (!element) {
|
||||||
|
alert(`Bad data source template! No HTML element found for selector '${key}'.`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
parsePatch(element, object);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function parsePatch(element, object) {
|
||||||
|
for (const [key, value] of Object.entries(object)) {
|
||||||
|
if (typeof value === 'object')
|
||||||
|
parsePatch(element[key], value);
|
||||||
|
else
|
||||||
|
element[key] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fadeBlock() {
|
||||||
|
startPage.style.opacity = 0;
|
||||||
|
await wait(0.2);
|
||||||
|
startPage.style.display = 'none';
|
||||||
|
await wait(0.2);
|
||||||
|
templatePage.style.display = 'block';
|
||||||
|
templatePage.style.opacity = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal__submit.addEventListener('click', async () => await proceed())
|
||||||
|
}
|
30
scripts/sdk.js
Normal file
30
scripts/sdk.js
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
async function fetchResource(id) {
|
||||||
|
const response = await fetch(ROUTES.GET_RESOURCE(id));
|
||||||
|
if (!response.ok) {
|
||||||
|
const json = await response.json();
|
||||||
|
|
||||||
|
throw new Error(`${json.error}: ${json.message}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (await response.json()).resource;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchStatus() {
|
||||||
|
const response = await fetch(ROUTES.STATUS);
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const json = await response.json();
|
||||||
|
|
||||||
|
throw new Error(`${json.error}: ${json.message}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const json = await response.json();
|
||||||
|
if (json.status !== 'ok') {
|
||||||
|
throw new Error('API status is not OK. Something went wrong.');
|
||||||
|
}
|
||||||
|
|
||||||
|
return json;
|
||||||
|
}
|
||||||
|
|
||||||
|
// on error, provide alert to the user
|
||||||
|
window.addEventListener('unhandledrejection', event => alert(event.reason));
|
72
style.css
Normal file
72
style.css
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
@import url('data/custom-style.css');
|
||||||
|
@import url('https://fonts.googleapis.com/css2?family=Raleway:ital,wght@0,100..900;1,100..900&display=swap');
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--text: #fff;
|
||||||
|
--background: #2f3136;
|
||||||
|
--button-color: #a8c0ff;
|
||||||
|
--button-color-hover: #c4d5ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
html, body {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: 'Raleway', sans-serif;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#internal__start-page, #internal__template {
|
||||||
|
transition: opacity 0.2s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
#internal__template {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#internal__start-page {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
gap: 5px;
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
align-content: center;
|
||||||
|
|
||||||
|
background-color: var(--background) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
#internal__start-page > *:is(h1, h2, h3, h4, h5, h6, p, span) {
|
||||||
|
color: var(--text);
|
||||||
|
}
|
||||||
|
|
||||||
|
#internal__start-page > input {
|
||||||
|
border-radius: 10px;
|
||||||
|
text-align: center;
|
||||||
|
height: 50px;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#internal__start-page > input:focus {
|
||||||
|
border: none;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#internal__start-page > button {
|
||||||
|
border: none;
|
||||||
|
outline: none;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
height: 50px;
|
||||||
|
min-width: 100px;
|
||||||
|
border-radius: 10px;
|
||||||
|
background-color: var(--button-color);
|
||||||
|
transition: background-color 0.2s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
#internal__start-page > button:hover {
|
||||||
|
background-color: var(--button-color-hover);
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user