├── LICENSE.md
├── README.md
├── examples
└── geosearch-airports
│ ├── .gitignore
│ ├── LICENSE.md
│ ├── README.md
│ ├── bun.lockb
│ ├── index.html
│ ├── package-lock.json
│ ├── package.json
│ ├── postcss.config.js
│ ├── public
│ ├── edit-pen-icon.webp
│ ├── hand-finger-click-icon.webp
│ ├── orama-geosearch.svg
│ └── orama.svg
│ ├── src
│ ├── App.jsx
│ ├── assets
│ │ └── react.svg
│ ├── components
│ │ ├── DrawControl
│ │ │ └── index.jsx
│ │ └── Map
│ │ │ └── index.jsx
│ ├── data
│ │ └── records-formatted.json
│ ├── index.css
│ ├── lib
│ │ └── orama.js
│ └── main.jsx
│ ├── tailwind.config.js
│ └── vite.config.js
└── package.json
/LICENSE.md:
--------------------------------------------------------------------------------
1 | Copyright 2023 OramaSearch Inc.
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 |
7 | http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Orama Examples
2 |
3 | This repository features a collection of sample projects demonstrating practical applications of Orama.
4 |
5 | ## Demo Lists
6 |
7 | - [Geosearch - Airport map](/examples/geosearch-airports/) [[live demo](https://orama-examples-geosearch-airports.vercel.app/)]. Search all the airports in the world by drawing on a map!
8 |
9 | ## License
10 | [Apache 2.0](/LICENSE.md)
--------------------------------------------------------------------------------
/examples/geosearch-airports/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 |
10 | .env
11 |
12 | node_modules
13 | dist
14 | dist-ssr
15 | *.local
16 |
17 | # Editor directories and files
18 | .vscode/*
19 | !.vscode/extensions.json
20 | .idea
21 | .DS_Store
22 | *.suo
23 | *.ntvs*
24 | *.njsproj
25 | *.sln
26 | *.sw?
27 |
--------------------------------------------------------------------------------
/examples/geosearch-airports/LICENSE.md:
--------------------------------------------------------------------------------
1 | Copyright 2023 OramaSearch Inc.
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 |
7 | http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
--------------------------------------------------------------------------------
/examples/geosearch-airports/README.md:
--------------------------------------------------------------------------------
1 | # Orama Geosearch
2 |
3 | Simple Vite + React app to demonstrate how to use Orama Geosearch with MapBox.
4 |
5 | Live demo: [https://orama-examples-geosearch-airports.vercel.app](https://orama-examples-geosearch-airports.vercel.app)
6 |
7 | # License
8 | [Apache 2.0](/LICENSE.md)
--------------------------------------------------------------------------------
/examples/geosearch-airports/bun.lockb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oramasearch/examples/c381192253c725f8c85a552d6201f05763421e87/examples/geosearch-airports/bun.lockb
--------------------------------------------------------------------------------
/examples/geosearch-airports/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Orama Demo - Geosearch
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/examples/geosearch-airports/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "geosearch-airports",
3 | "private": true,
4 | "version": "0.0.0",
5 | "type": "module",
6 | "scripts": {
7 | "dev": "vite",
8 | "build": "vite build",
9 | "lint": "standard --fix .",
10 | "preview": "vite preview"
11 | },
12 | "dependencies": {
13 | "@orama/orama": "^2.0.0-beta.1",
14 | "@types/mapbox-gl": "^2.7.15",
15 | "classnames": "^2.3.2",
16 | "mapbox-gl": "^2.15.0",
17 | "react": "^18.2.0",
18 | "react-dom": "^18.2.0",
19 | "react-icons": "^4.11.0",
20 | "react-map-gl": "^7.1.6",
21 | "terra-draw": "^0.0.1-alpha.48"
22 | },
23 | "devDependencies": {
24 | "@typescript-eslint/eslint-plugin": "^6.0.0",
25 | "@typescript-eslint/parser": "^6.0.0",
26 | "@vitejs/plugin-react-swc": "^3.3.2",
27 | "autoprefixer": "^10.4.16",
28 | "eslint": "^8.45.0",
29 | "eslint-plugin-react-hooks": "^4.6.0",
30 | "eslint-plugin-react-refresh": "^0.4.3",
31 | "postcss": "^8.4.31",
32 | "standard": "^17.1.0",
33 | "tailwindcss": "^3.3.3",
34 | "typescript": "^5.0.2",
35 | "vite": "^4.4.5",
36 | "vite-plugin-top-level-await": "^1.3.1"
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/examples/geosearch-airports/postcss.config.js:
--------------------------------------------------------------------------------
1 | export default {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {}
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/examples/geosearch-airports/public/edit-pen-icon.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oramasearch/examples/c381192253c725f8c85a552d6201f05763421e87/examples/geosearch-airports/public/edit-pen-icon.webp
--------------------------------------------------------------------------------
/examples/geosearch-airports/public/hand-finger-click-icon.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oramasearch/examples/c381192253c725f8c85a552d6201f05763421e87/examples/geosearch-airports/public/hand-finger-click-icon.webp
--------------------------------------------------------------------------------
/examples/geosearch-airports/public/orama-geosearch.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
--------------------------------------------------------------------------------
/examples/geosearch-airports/public/orama.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
--------------------------------------------------------------------------------
/examples/geosearch-airports/src/App.jsx:
--------------------------------------------------------------------------------
1 | import { useState } from 'react'
2 | import { OramaMap } from './components/Map'
3 |
4 | function App() {
5 | const [searchResults, setSearchResults] = useState()
6 |
7 | return (
8 | <>
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
Airports
20 |
21 | {
22 | searchResults
23 | ?
{searchResults?.length ?? 0} results
24 | :
No results
25 | }
26 |
27 |
43 |
44 |
45 | >
46 | )
47 | }
48 |
49 | export default App
50 |
--------------------------------------------------------------------------------
/examples/geosearch-airports/src/assets/react.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/examples/geosearch-airports/src/components/DrawControl/index.jsx:
--------------------------------------------------------------------------------
1 | import { TerraDraw, TerraDrawMapboxGLAdapter, TerraDrawPolygonMode, TerraDrawSelectMode } from 'terra-draw'
2 | import { useMap } from 'react-map-gl';
3 | import { useState, useEffect } from 'react'
4 |
5 | export function DrawControl(props) {
6 |
7 | const { current: map } = useMap();
8 |
9 | const [mode, setMode] = useState('static')
10 | const [draw, setDraw] = useState()
11 |
12 | const onChangeCallback = (ids, event) => {
13 | const snapshot = draw.getSnapshot().filter((feature) => feature.geometry.type === 'Polygon')
14 | const polygon = snapshot.find((polygon) => polygon.id === ids[0])
15 |
16 | if (event === 'create' && polygon) {
17 | props.onCreate(polygon)
18 | }
19 | if (event === 'update' && polygon) {
20 | props.onUpdate(polygon)
21 | }
22 | if (event === 'delete') {
23 | props.onDelete(ids)
24 | }
25 | }
26 |
27 | useEffect(() => {
28 | if (!map || draw) {
29 | return
30 | }
31 |
32 | const drawInstance = new TerraDraw({
33 | adapter: new TerraDrawMapboxGLAdapter({
34 | map: map.getMap(),
35 | minPixelDragDistanceSelecting: 1
36 | }),
37 | modes: [new TerraDrawPolygonMode({
38 | styles: {
39 | fillColor: '#f6cd63',
40 | outlineColor: '#faa38f',
41 | outlineWidth: 3,
42 | fillOpacity: 0.1,
43 | closingPointWidth: 5,
44 | closingPointColor: '#faa38f',
45 | closingPointOutlineWidth: 2,
46 | closingPointOutlineColor: '#f5f5f5',
47 | }
48 | }), new TerraDrawSelectMode({
49 | dragEventThrottle: 3,
50 | flags: {
51 | polygon: {
52 | feature: {
53 | draggable: true,
54 | rotateable: true,
55 | scaleable: true,
56 | coordinates: {
57 | midpoints: true,
58 | draggable: true,
59 | deletable: true,
60 | },
61 | },
62 | }
63 | },
64 | styles: {
65 | selectedPolygonColor: '#f6cd63',
66 | selectedPolygonOutlineColor: '#faa38f',
67 | outlineWidth: 3,
68 | selectionPolygonFillOpacity: 0.2,
69 | selectionPointWidth: 5,
70 | selectionPointColor: '#faa38f',
71 | selectionPointOutlineWidth: 2,
72 | selectionPointOutlineColor: '#f5f5f5',
73 | midPointColor: '#f6cd63',
74 | midPointOutlineColor: '#f5f5f5',
75 | midPointWidth: 3,
76 | midPointOutlineWidth: 2
77 | }
78 | })]
79 | })
80 |
81 | drawInstance.start()
82 | drawInstance.setMode('polygon')
83 | setMode('polygon')
84 | setDraw(drawInstance)
85 |
86 | // Ensure clear up on dismount
87 | return () => {
88 | drawInstance.stop()
89 | }
90 | }, [map])
91 |
92 |
93 | useEffect(() => {
94 | const callback = (ids, event) => {
95 | onChangeCallback(ids, event)
96 | }
97 |
98 | if (draw) {
99 | draw.on('change', callback)
100 | }
101 |
102 | // Ensure callbacks are deleted and created correcltly
103 | return () => {
104 | if (draw && callback) {
105 | draw.off('change', callback)
106 | }
107 | }
108 |
109 | }, [draw, onChangeCallback])
110 |
111 | return <>
112 |
113 |
{
118 | if (draw) {
119 | draw.setMode('polygon');
120 | setMode('polygon');
121 | }
122 | }}
123 | style={{
124 | background: "#f5f5f5",
125 | top: "30px",
126 | right: "30px",
127 | zIndex: 1,
128 | height: "30px",
129 | width: "30px",
130 | position: "absolute",
131 | borderRadius: "2px",
132 | outline: mode === 'polygon' ? "solid 3px #f6cd63" : 'none'
133 | }}>
134 |
141 |
142 |
143 |
144 |
{
149 | if (draw) {
150 | draw.setMode('select');
151 | setMode('select');
152 | }
153 | }}
154 | style={{
155 | background: "#f5f5f5",
156 | top: "70px",
157 | right: "30px",
158 | zIndex: 1,
159 | height: "30px",
160 | width: "30px",
161 | position: "absolute",
162 | borderRadius: "2px",
163 | outline: mode === 'select' ? "solid 3px #f6cd63" : 'none'
164 | }}>
165 |
172 |
173 |
174 | >
175 | }
176 |
177 | DrawControl.defaultProps = {
178 | onCreate: () => { },
179 | onUpdate: () => { },
180 | onDelete: () => { }
181 | }
182 |
--------------------------------------------------------------------------------
/examples/geosearch-airports/src/components/Map/index.jsx:
--------------------------------------------------------------------------------
1 | import { useState, useEffect, useMemo } from 'react'
2 | import Map, { Marker } from 'react-map-gl'
3 | import { search } from '@orama/orama'
4 | import { FaLocationPin } from 'react-icons/fa6'
5 | import { orama } from '../../lib/orama'
6 | import { DrawControl } from '../DrawControl'
7 |
8 | const VITE_MAPBOX_TOKEN = import.meta.env.VITE_MAPBOX_TOKEN
9 |
10 | if (!VITE_MAPBOX_TOKEN) {
11 | throw Error('VITE_MAPBOX_TOKEN not set in .env file')
12 | }
13 |
14 | export function OramaMap({ onPolygonChange }) {
15 | const [polygons, setPolygons] = useState({})
16 | const [uniqueLocations, setUniqueLocations] = useState([])
17 |
18 | useEffect(() => {
19 | const polygonIds = Object.keys(polygons)
20 |
21 | const oramaPromises = []
22 |
23 | for (const id of polygonIds) {
24 | const polygon = polygons[id]
25 | const coordinates = []
26 |
27 | if (!polygon.geometry.coordinates || !polygon.geometry.coordinates[0]) {
28 | return
29 | }
30 |
31 | for (const point of polygon.geometry.coordinates[0]) {
32 | coordinates.push({ lat: point[1], lon: point[0] })
33 | }
34 |
35 | oramaPromises.push(search(orama, {
36 | limit: 10_000,
37 | where: {
38 | location: {
39 | polygon: {
40 | coordinates
41 | }
42 | }
43 | }
44 | })
45 | .then((data) => {
46 | if (!data || !data.hits) {
47 | return
48 | }
49 |
50 | return { id: polygon.id, hits: data.hits }
51 | })
52 | .catch(console.error))
53 |
54 | }
55 |
56 | // Finally update the search results for all polygons
57 | Promise.all(oramaPromises).then((results) => {
58 | const newSearchResults = {}
59 | results.forEach((result) => {
60 | if (!result) {
61 | return
62 | }
63 | newSearchResults[result.id] = result.hits
64 | })
65 |
66 | const uniqueLocationIds = new Set()
67 | const uniqueLocations = []
68 |
69 | // Ensure that locations aren't added twice as this will throw an error
70 | Object.keys(newSearchResults).forEach((polygonId) => {
71 | newSearchResults[polygonId].forEach((location) => {
72 | if (!uniqueLocationIds.has(location)) {
73 | uniqueLocationIds.add(location.id)
74 | uniqueLocations.push(location)
75 | }
76 | })
77 | })
78 |
79 | onPolygonChange(uniqueLocations)
80 | setUniqueLocations(uniqueLocations)
81 | })
82 |
83 | }, [polygons])
84 |
85 |
86 | const pinMarkers = useMemo(() => {
87 | if (!uniqueLocations || uniqueLocations.length === 0) {
88 | return []
89 | }
90 |
91 | return uniqueLocations.map((hit) => (
92 | {
97 | e.originalEvent.stopPropagation()
98 | }}
99 | >
100 |
101 | ))
102 | }, [uniqueLocations])
103 |
104 | return <>
105 |
118 | {
120 | setPolygons({
121 | ...polygons,
122 | [polygon.id]: polygon
123 | })
124 | }}
125 | onUpdate={(polygon) => {
126 | setPolygons({
127 | ...polygons,
128 | [polygon.id]: polygon
129 | })
130 | }}
131 | onDelete={(deletedIds) => {
132 | setPolygons((currentPolygons) => {
133 |
134 | const newPolygons = {}
135 | Object.keys(currentPolygons).forEach((polygonId) => {
136 | if (!deletedIds.includes(polygonId)) {
137 | newPolygons[polygonId] = currentPolygons[polygonId]
138 | }
139 | })
140 |
141 | return newPolygons
142 | })
143 | }}
144 | />
145 | {pinMarkers}
146 |
147 | >
148 | }
149 |
--------------------------------------------------------------------------------
/examples/geosearch-airports/src/index.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
5 | ul::-webkit-scrollbar {
6 | @apply w-2;
7 | }
8 |
9 | ul::-webkit-scrollbar-track {
10 | @apply bg-transparent;
11 | }
12 |
13 | ul::-webkit-scrollbar-thumb {
14 | @apply bg-zinc-950 rounded-lg;
15 | }
16 |
17 | ul::-webkit-scrollbar-thumb:hover {
18 | @apply bg-gray-950;
19 | }
--------------------------------------------------------------------------------
/examples/geosearch-airports/src/lib/orama.js:
--------------------------------------------------------------------------------
1 | import { create, insertMultiple } from '@orama/orama'
2 | import data from '../data/records-formatted.json'
3 |
4 | const db = await create({
5 | schema: {
6 | id: 'string',
7 | iata: 'string',
8 | country: 'string',
9 | city: 'string',
10 | links: 'number',
11 | location: 'geopoint'
12 | }
13 | })
14 |
15 | await insertMultiple(db, data)
16 |
17 | export const orama = db
18 |
--------------------------------------------------------------------------------
/examples/geosearch-airports/src/main.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactDOM from 'react-dom/client'
3 | import App from './App.jsx'
4 | import './index.css'
5 | import 'mapbox-gl/dist/mapbox-gl.css'
6 |
7 | ReactDOM.createRoot(document.getElementById('root')).render(
8 |
9 |
10 |
11 | )
12 |
--------------------------------------------------------------------------------
/examples/geosearch-airports/tailwind.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('tailwindcss').Config} */
2 | export default {
3 | content: [
4 | './index.html',
5 | './src/**/*.{js,ts,jsx,tsx}'
6 | ],
7 | theme: {
8 | extend: {}
9 | },
10 | plugins: []
11 | }
12 |
--------------------------------------------------------------------------------
/examples/geosearch-airports/vite.config.js:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite'
2 | import react from '@vitejs/plugin-react-swc'
3 | import topLevelAwait from 'vite-plugin-top-level-await'
4 |
5 | // https://vitejs.dev/config/
6 | export default defineConfig({
7 | plugins: [react(), topLevelAwait()]
8 | })
9 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "orama-examples",
3 | "version": "0.0.1",
4 | "description": "Orama Examples",
5 | "workspaces": [
6 | "packages/*"
7 | ],
8 | "scripts": {},
9 | "author": {
10 | "name": "Michele Riva",
11 | "email": "michele.riva@oramasearch.com",
12 | "url": "https://github.com/MicheleRiva",
13 | "author": true
14 | },
15 | "license": "Apache-2.0"
16 | }
--------------------------------------------------------------------------------