add: dcd files

This commit is contained in:
sadorowo 2024-03-10 13:39:10 +01:00
parent f847425a52
commit 13b5e68bf6
8 changed files with 264 additions and 0 deletions

BIN
.DS_Store vendored Normal file

Binary file not shown.

73
README.md Normal file
View 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
View 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
View File

@ -0,0 +1 @@
/* If rule statements doesn't work, add !important to the end of it. */

23
index.html Normal file
View 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
View 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
View 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
View 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);
}