Skip to content

Commit 6f40e55

Browse files
Add Connecting Miro to Firebase app example (#59)
* Add new examples to repo readme * Add connect-backend example to repo * remove comment from CODEOWNERS * Update naming
1 parent 3ffc7fb commit 6f40e55

14 files changed

+359
-15
lines changed

CODEOWNERS

-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1 @@
1-
# In this example, @doctocat owns any file in the `/docs`
2-
# directory in the root of your repository and any of its
3-
# subdirectories.
41
/examples/ @addisonschultz

README.md

+15-12
Original file line numberDiff line numberDiff line change
@@ -22,22 +22,25 @@ yarn create miro-app
2222

2323
| | Description |
2424
| ------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
25-
| [drag-and-drop](examples/drag-and-drop) | This app example allows dragging and dropping images from your app onto the board. |
26-
| [stickynotes-to-shapes](examples/stickynotes-to-shapes) | This app example allows selecting several sticky notes on the board, clicking the app button in the app toolbar, and replacing the selected sticky notes with shapes. |
27-
| [template-builder](examples/template-builder) | This app example creates and positions on the board multiple items of different types and it helps create and render custom interfaces in the library. |
28-
| [calendar](examples/calendar) | This app example adds a calendar made with shapes and text for a given month and year. |
29-
| [wordle](examples/wordle) | This app example creates a Wordle-like game using the Miro Web SDK. |
30-
| [blob-maker](examples/blob-maker) | This app example creates a drag-and-drop blobmaker using the Miro Web SDK. |
31-
| [youtube-room](examples/youtube-room) | This app example syncs a YouTube player across multiple users through Socket.IO. |
25+
| [drag-and-drop](examples/drag-and-drop) | This example shows you how to drag and drop images from your app onto the board. |
26+
| [connect-firebase](examples/connect-firebase) | This example shows you how to connect an SDK app to a Firebase backend. |
27+
| [stickynotes-to-shapes](examples/stickynotes-to-shapes) | This example allows you to select several stickies, click the plugin button in the bottom bar, and replace any selected stickies with shapes. |
28+
| [template-builder](examples/template-builder) | This example shows how to create and position on the board multiple widgets of different types and render create custom interfaces in the library. |
29+
| [calendar](examples/calendar) | This example shows you how to add a calendar made with shapes and text for a given month and year. |
30+
| [wordle](examples/wordle) | This example shows you how to create a wordle-like game using Miro's Web SDK. |
31+
| [blob-maker](examples/blob-maker) | This example shows you how to create a drag and drop blobmaker using Miro's Web SDK. |
32+
| [youtube-room](examples/youtube-room) | This example shows you how to sync a Youtube player across multiple users through Socket.IO. |
3233

3334
<p>&nbsp;</p>
3435

35-
## Miro REST API
36+
## REST APIs
37+
38+
| | Description |
39+
| ----------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
40+
| [python_oauth](examples/oauth/python) | This python sample demonstrates how to implement the Oauth 2.0 authorization code flow in Miro. |
41+
| [node_oauth](examples/oauth/node) | This NodeJS sample demonstrates how to implement the Oauth 2.0 authorization code flow in Miro and make an API request to a Miro endpoint. |
42+
| [rest-stickies-csv](examples/rest-stickies-csv) | This NodeJS sample app uses server side rendering (HandlebarsJS) to provide a lightweight, CRUD-oriented REST example in the browser for Miro's Sticky Notes and Tags APIs. It demonstrates a structured > unstructured use case via CSV import, creating Miro Sticky Notes with Tags based on CSV data |
3643

37-
| | Description |
38-
| ------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
39-
| [python_oauth](examples/oauth/python) | This Python app example implements the OAuth 2.0 authorization code flow in Miro. |
40-
| [node_oauth](examples/oauth/node) | This Node.js app example implements the OAuth 2.0 authorization code flow in Miro, and it makes an API request to a Miro endpoint. |
4144

4245
<p>&nbsp;</p>
4346

examples/connect-firebase/.gitignore

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2+
3+
# dependencies
4+
/node_modules
5+
/.pnp
6+
.pnp.js
7+
8+
# testing
9+
/coverage
10+
11+
# misc
12+
.DS_Store
13+
*.pem
14+
15+
# debug
16+
npm-debug.log*
17+
yarn-debug.log*
18+
yarn-error.log*
19+
20+
# local env files
21+
22+
.env
23+
dist

examples/connect-firebase/README.md

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# Connecting Miro to a Firebase Backend
2+
3+
This project follows our guide "How to connect the Web SDK to a backend", and contains the finished code. Read through the guide for an in-depth walkthrough on setting this project up from scratch.
4+
5+
## How to start:
6+
7+
- Run `yarn` or `npm install` to install dependencies
8+
- Open `src/app.js`, and replace the `firebaseConfig` object with the settings from your Firebase project
9+
10+
```js
11+
const firebaseConfig = {
12+
apiKey: "YOUR-API-KEY",
13+
authDomain: "YOUR-DOMAIN.firebaseapp.com",
14+
databaseURL: "https://YOUR-DOMAIN-rtdb.firebaseio.com",
15+
projectId: "YOUR-PROJECT-ID",
16+
storageBucket: "YOUR-BUCKET.appspot.com",
17+
messagingSenderId: "YOUR-SENDER-ID",
18+
appId: "YOUR-APP-ID",
19+
};
20+
```
21+
22+
- Run `yarn start` or `npm start` to start developing, you should have a URL
23+
that looks like this
24+
25+
```
26+
http://localhost:3000
27+
```
28+
29+
- Paste the URL in `App URL` in your app settings
30+
- open a board & you should see your app in the main toolbar when you click the
31+
three dots.
32+
33+
## How to build the app:
34+
35+
Run `yarn run build` or `npm run build` and this will generate a static output
36+
inside `dist/` which you can host on static hosting service.
37+
38+
### About the app
39+
40+
This app is using [vite](https://vitejs.dev/) so you can check the documentation
41+
if you want to modify `vite.config.js` configuration if needed.

examples/connect-firebase/app.html

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="utf-8" />
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
6+
<link rel="stylesheet" href="/src/assets/style.css" />
7+
<script src="https://miro.com/app/static/sdk/v2/miro.js"></script>
8+
</head>
9+
<body>
10+
11+
<div class="grid wrapper">
12+
<div class="cs1 ce12">
13+
<button id="save-btn" class="button button-primary">
14+
Save Selection as Template
15+
</button>
16+
<div class="form-group">
17+
<input id="search-bar" class="input" type="text" placeholder="Search templates"/>
18+
</div>
19+
<p>Drag templates into your board</p>
20+
<div id="templates"></div>
21+
</div>
22+
</div>
23+
24+
<script type="module" src="/src/app.js"></script>
25+
</body>
26+
</html>

examples/connect-firebase/index.html

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="utf-8" />
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
6+
<link rel="stylesheet" href="/src/assets/style.css" />
7+
<script src="https://miro.com/app/static/sdk/v2/miro.js"></script>
8+
</head>
9+
<body>
10+
<div class="grid container">
11+
<div class="cs1 ce12">
12+
<img src="/src/assets/welcome.png" alt="" />
13+
</div>
14+
<div class="cs1 ce12">
15+
<h1>Great, your app is running locally</h1>
16+
<p>
17+
You can now create your Developer team to get your app running in
18+
Miro.
19+
</p>
20+
</div>
21+
<div class="cs1 ce12">
22+
<a
23+
class="button button-primary"
24+
href="https://beta.developers.miro.com/docs/create-a-developer-team"
25+
target="_blank"
26+
>
27+
Create a Developer team
28+
</a>
29+
</div>
30+
</div>
31+
<script type="module" src="/src/index.js"></script>
32+
</body>
33+
</html>
+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"compilerOptions": {
3+
"typeRoots": ["./node_modules/@types", "./node_modules/@mirohq"]
4+
},
5+
"include": ["src"],
6+
"exclude": ["node_modules"]
7+
}
+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
"name": "miro-templates-app",
3+
"version": "0.1.0",
4+
"licence": "MIT",
5+
"scripts": {
6+
"start": "vite",
7+
"build": "vite build",
8+
"serve": "vite preview"
9+
},
10+
"dependencies": {
11+
"mirotone": "2.0.5"
12+
},
13+
"devDependencies": {
14+
"@mirohq/websdk-types": "latest",
15+
"vite": "2.7.13"
16+
}
17+
}

examples/connect-firebase/src/app.js

+129
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
import { initializeApp } from "https://www.gstatic.com/firebasejs/9.6.10/firebase-app.js";
2+
import { getDatabase, ref, set, get } from "https://www.gstatic.com/firebasejs/9.6.10/firebase-database.js";
3+
4+
const firebaseConfig = {
5+
apiKey: "YOUR-API-KEY",
6+
authDomain: "YOUR-DOMAIN.firebaseapp.com",
7+
databaseURL: "https://YOUR-DOMAIN-rtdb.firebaseio.com",
8+
projectId: "YOUR-PROJECT-ID",
9+
storageBucket: "YOUR-BUCKET.appspot.com",
10+
messagingSenderId: "YOUR-SENDER-ID",
11+
appId: "YOUR-APP-ID"
12+
};
13+
14+
// Initialize Firebase
15+
const app = initializeApp(firebaseConfig);
16+
const db = getDatabase( app );
17+
18+
async function getTemplatesFromDB( userId ) {
19+
const data = await get( ref( db, "templates/" + userId ) );
20+
if( data.exists() ) {
21+
return data.val();
22+
}
23+
return [];
24+
}
25+
26+
function saveTemplatesToDB( userId, templates ) {
27+
set( ref( db, "templates/" + userId ), templates );
28+
}
29+
30+
let templates = [];
31+
let filter = "";
32+
33+
window.saveTemplate = ( userId, name, items ) => {
34+
// Calculate center
35+
let totalX = 0, totalY = 0;
36+
items.forEach( item => {
37+
totalX += item.x;
38+
totalY += item.y;
39+
});
40+
const center = { x: totalX / items.length, y: totalY / items.length };
41+
42+
// Save to the cloud
43+
templates.push( { name, items, center });
44+
saveTemplatesToDB( userId, templates );
45+
displayTemplates( userId, templates );
46+
}
47+
48+
window.deleteTemplate = ( userId, index ) => {
49+
templates.splice( index, 1 );
50+
saveTemplatesToDB( userId, templates );
51+
displayTemplates( userId, templates );
52+
}
53+
54+
function displayTemplates( userId, templates ) {
55+
const templateList = document.getElementById( "templates" );
56+
templateList.innerHTML = "";
57+
templates.forEach( ( t, i ) => {
58+
if( !t.name.toLowerCase().includes( filter.toLowerCase() ) ) { return; }
59+
templateList.innerHTML +=
60+
`<div class="cs1 ce12 miro-draggable" data-index="${i}" style="padding: 5px; margin: 5px; border: 5px solid black;">
61+
<h2>${t.name} - <span onclick="deleteTemplate('${userId}', ${i})">x</span></h2>
62+
</div>`;
63+
});
64+
}
65+
66+
async function createWidgetFromJson( item, x, y ) {
67+
const clone = Object.assign( {}, item, { x: item.x + x, y: item.y + y } );
68+
switch( clone.type ) {
69+
case "card":
70+
await miro.board.createCard( clone );
71+
break;
72+
case "frame":
73+
await miro.board.createFrame( clone );
74+
break;
75+
case "shape":
76+
await miro.board.createShape( clone );
77+
break;
78+
case "sticky_note":
79+
await miro.board.createStickyNote( clone );
80+
break;
81+
case "text":
82+
await miro.board.createText( clone );
83+
break;
84+
}
85+
}
86+
87+
async function init() {
88+
// Get the user
89+
const user = await miro.board.getUserInfo();
90+
91+
// Load templates from the cloud
92+
templates = await getTemplatesFromDB( user.id );
93+
displayTemplates( user.id, templates );
94+
95+
// Save button handler
96+
document.getElementById( "save-btn" ).onclick = async () => {
97+
const name = prompt( "Please enter a name for the template" );
98+
// Save items
99+
const items = await miro.board.getSelection();
100+
// Remove unnecessary properties
101+
items.forEach( item => {
102+
delete item.id;
103+
delete item.parentId;
104+
delete item.height; // Note: only save the width and not the height
105+
delete item.createdAt;
106+
delete item.createdBy;
107+
delete item.modifiedAt;
108+
delete item.modifiedBy;
109+
});
110+
saveTemplate( user.id, name, items );
111+
};
112+
113+
// Search handler
114+
document.getElementById( "search-bar" ).addEventListener( "input", (e) => {
115+
filter = e.target.value;
116+
displayTemplates( user.id, templates );
117+
});
118+
119+
// Drag-n-drop handler
120+
miro.board.ui.on( "drop", async ({ x, y, target }) => {
121+
const index = target.getAttribute( "data-index" );
122+
const template = templates[ index ];
123+
template.items.forEach( item => {
124+
createWidgetFromJson( item, x - template.center.x, y - template.center.y );
125+
});
126+
});
127+
}
128+
129+
init();
Loading
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
@import 'mirotone/dist/styles.css';
2+
3+
*,
4+
*:before,
5+
*:after {
6+
box-sizing: border-box;
7+
}
8+
9+
html {
10+
height: 100%;
11+
background-color: #eee;
12+
font-family: Inter, ui-sans-serif, system-ui, -apple-system,
13+
BlinkMacSystemFont, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans,
14+
sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol,
15+
Noto Color Emoji;
16+
}
17+
18+
body {
19+
height: 100%;
20+
line-height: 1.5;
21+
margin: 0;
22+
padding: 2rem;
23+
}
24+
25+
.container {
26+
max-width: 580px;
27+
margin: 0 auto;
28+
background-color: var(--indigo50);
29+
padding: var(--space-xxxlarge);
30+
border-radius: var(--border-radius-xlarge);
31+
}
32+
33+
img {
34+
max-width: 100%;
35+
}
36+
37+
.button {
38+
text-decoration: none;
39+
}
70.5 KB
Loading
+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
async function init() {
2+
miro.board.ui.on('icon:click', async () => {
3+
await miro.board.ui.openPanel({url: 'app.html'});
4+
});
5+
}
6+
7+
init();
+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
const path = require('path');
2+
const fs = require('fs');
3+
const {defineConfig} = require('vite');
4+
5+
// make sure vite picks up all html files in root, needed for vite build
6+
const allHtmlEntries = fs
7+
.readdirSync('.')
8+
.filter((file) => path.extname(file) === '.html')
9+
.reduce((acc, file) => {
10+
acc[path.basename(file, '.html')] = path.resolve(__dirname, file);
11+
12+
return acc;
13+
}, {});
14+
15+
// https://vitejs.dev/config/
16+
module.exports = defineConfig({
17+
build: {
18+
rollupOptions: {
19+
input: allHtmlEntries,
20+
},
21+
},
22+
});

0 commit comments

Comments
 (0)