Spaces:
Running
Running
clean up
Browse filesThis view is limited to 50 files because it contains too many changes.
See raw diff
- frontend/src/App.tsx +0 -2
- frontend/src/components/HeaderNav.tsx +0 -2
- frontend/src/pages/DemoPage.tsx +0 -953
- frontend/src/pages/ExplorePage/ExplorePage.tsx +12 -39
- frontend/src/pages/MapDetailsPage/MapDetailPage.tsx +107 -173
- frontend/src/pages/UploadPage/UploadPage.tsx +3 -53
- go-web-app-develop/.changeset/README.md +0 -8
- go-web-app-develop/.changeset/config.json +0 -15
- go-web-app-develop/.changeset/lovely-kids-boil.md +0 -5
- go-web-app-develop/.changeset/pre.json +0 -15
- go-web-app-develop/.changeset/solid-clubs-care.md +0 -8
- go-web-app-develop/.changeset/sweet-gifts-cheer.md +0 -9
- go-web-app-develop/.changeset/whole-lions-guess.md +0 -7
- go-web-app-develop/.dockerignore +0 -148
- go-web-app-develop/.github/ISSUE_TEMPLATE/01_bug_report.yml +0 -92
- go-web-app-develop/.github/ISSUE_TEMPLATE/02_feature_request.yml +0 -39
- go-web-app-develop/.github/ISSUE_TEMPLATE/03_epic_request.yml +0 -37
- go-web-app-develop/.github/ISSUE_TEMPLATE/config.yml +0 -5
- go-web-app-develop/.github/dependabot.yml +0 -27
- go-web-app-develop/.github/pull_request_template.md +0 -30
- go-web-app-develop/.github/workflows/add-issue-to-backlog.yml +0 -16
- go-web-app-develop/.github/workflows/chromatic.yml +0 -127
- go-web-app-develop/.github/workflows/ci.yml +0 -304
- go-web-app-develop/.github/workflows/publish-nginx-serve.yml +0 -147
- go-web-app-develop/.github/workflows/publish-storybook-nginx-serve.yml +0 -127
- go-web-app-develop/.gitignore +0 -43
- go-web-app-develop/.npmrc +0 -1
- go-web-app-develop/COLLABORATING.md +0 -18
- go-web-app-develop/CONTRIBUTING.md +0 -81
- go-web-app-develop/LICENSE +0 -21
- go-web-app-develop/README.md +0 -117
- go-web-app-develop/app/CHANGELOG.md +0 -729
- go-web-app-develop/app/env.ts +0 -29
- go-web-app-develop/app/eslint.config.js +0 -165
- go-web-app-develop/app/index.html +0 -69
- go-web-app-develop/app/package.json +0 -119
- go-web-app-develop/app/postcss.config.cjs +0 -8
- go-web-app-develop/app/public/go-icon.svg +0 -4
- go-web-app-develop/app/scripts/translatte/README.md +0 -59
- go-web-app-develop/app/scripts/translatte/commands/applyMigrations.test.ts +0 -104
- go-web-app-develop/app/scripts/translatte/commands/applyMigrations.ts +0 -177
- go-web-app-develop/app/scripts/translatte/commands/exportMigration.ts +0 -62
- go-web-app-develop/app/scripts/translatte/commands/generateMigration.test.ts +0 -102
- go-web-app-develop/app/scripts/translatte/commands/generateMigration.ts +0 -195
- go-web-app-develop/app/scripts/translatte/commands/lint.test.ts +0 -75
- go-web-app-develop/app/scripts/translatte/commands/lint.ts +0 -106
- go-web-app-develop/app/scripts/translatte/commands/listMigrations.test.ts +0 -48
- go-web-app-develop/app/scripts/translatte/commands/listMigrations.ts +0 -11
- go-web-app-develop/app/scripts/translatte/commands/mergeMigrations.test.ts +0 -371
- go-web-app-develop/app/scripts/translatte/commands/mergeMigrations.ts +0 -220
frontend/src/App.tsx
CHANGED
@@ -8,7 +8,6 @@ import AnalyticsPage from './pages/AnalyticsPage';
|
|
8 |
import ExplorePage from './pages/ExplorePage';
|
9 |
import HelpPage from './pages/HelpPage';
|
10 |
import MapDetailPage from './pages/MapDetailsPage';
|
11 |
-
import DemoPage from './pages/DemoPage';
|
12 |
|
13 |
import AdminPage from './pages/AdminPage/AdminPage';
|
14 |
import { FilterProvider } from './contexts/FilterContext';
|
@@ -23,7 +22,6 @@ const router = createHashRouter([
|
|
23 |
{ path: '/analytics', element: <AnalyticsPage /> },
|
24 |
{ path: '/explore', element: <ExplorePage /> },
|
25 |
{ path: '/help', element: <HelpPage /> },
|
26 |
-
{ path: '/demo', element: <DemoPage /> },
|
27 |
|
28 |
{ path: '/admin', element: <AdminPage /> },
|
29 |
{ path: '/map/:mapId', element: <MapDetailPage /> },
|
|
|
8 |
import ExplorePage from './pages/ExplorePage';
|
9 |
import HelpPage from './pages/HelpPage';
|
10 |
import MapDetailPage from './pages/MapDetailsPage';
|
|
|
11 |
|
12 |
import AdminPage from './pages/AdminPage/AdminPage';
|
13 |
import { FilterProvider } from './contexts/FilterContext';
|
|
|
22 |
{ path: '/analytics', element: <AnalyticsPage /> },
|
23 |
{ path: '/explore', element: <ExplorePage /> },
|
24 |
{ path: '/help', element: <HelpPage /> },
|
|
|
25 |
|
26 |
{ path: '/admin', element: <AdminPage /> },
|
27 |
{ path: '/map/:mapId', element: <MapDetailPage /> },
|
frontend/src/components/HeaderNav.tsx
CHANGED
@@ -67,7 +67,6 @@ export default function HeaderNav() {
|
|
67 |
(to === '/upload' && location.pathname === '/') ||
|
68 |
(to === '/explore' && location.pathname.startsWith('/map/'));
|
69 |
|
70 |
-
// Don't allow navigation to upload or home when already on upload page
|
71 |
const isUploadPage = location.pathname === "/upload" || location.pathname === "/";
|
72 |
const isUploadOrHomeNav = to === "/upload" || to === "/";
|
73 |
|
@@ -84,7 +83,6 @@ export default function HeaderNav() {
|
|
84 |
: 'hover:bg-white hover:shadow-md hover:scale-105'
|
85 |
}`}
|
86 |
onClick={() => {
|
87 |
-
// Prevent navigation to upload/home when already on upload page
|
88 |
if (isUploadPage && isUploadOrHomeNav) {
|
89 |
return;
|
90 |
}
|
|
|
67 |
(to === '/upload' && location.pathname === '/') ||
|
68 |
(to === '/explore' && location.pathname.startsWith('/map/'));
|
69 |
|
|
|
70 |
const isUploadPage = location.pathname === "/upload" || location.pathname === "/";
|
71 |
const isUploadOrHomeNav = to === "/upload" || to === "/";
|
72 |
|
|
|
83 |
: 'hover:bg-white hover:shadow-md hover:scale-105'
|
84 |
}`}
|
85 |
onClick={() => {
|
|
|
86 |
if (isUploadPage && isUploadOrHomeNav) {
|
87 |
return;
|
88 |
}
|
frontend/src/pages/DemoPage.tsx
DELETED
@@ -1,953 +0,0 @@
|
|
1 |
-
import { useState } from 'react';
|
2 |
-
import {
|
3 |
-
PageContainer,
|
4 |
-
Heading,
|
5 |
-
Button,
|
6 |
-
TextInput,
|
7 |
-
SelectInput,
|
8 |
-
MultiSelectInput,
|
9 |
-
SearchSelectInput,
|
10 |
-
SearchMultiSelectInput,
|
11 |
-
TextArea,
|
12 |
-
Checkbox,
|
13 |
-
Switch,
|
14 |
-
DateInput,
|
15 |
-
NumberInput,
|
16 |
-
PasswordInput,
|
17 |
-
RawFileInput,
|
18 |
-
Container,
|
19 |
-
Spinner,
|
20 |
-
ProgressBar,
|
21 |
-
StackedProgressBar,
|
22 |
-
KeyFigure,
|
23 |
-
PieChart,
|
24 |
-
BarChart,
|
25 |
-
IconButton,
|
26 |
-
ConfirmButton,
|
27 |
-
InputLabel,
|
28 |
-
InputHint,
|
29 |
-
InputSection,
|
30 |
-
BooleanInput,
|
31 |
-
BooleanOutput,
|
32 |
-
DateOutput,
|
33 |
-
NumberOutput,
|
34 |
-
TextOutput,
|
35 |
-
Footer,
|
36 |
-
NavigationTabList,
|
37 |
-
SegmentInput,
|
38 |
-
BlockLoading,
|
39 |
-
} from '@ifrc-go/ui';
|
40 |
-
import {
|
41 |
-
UploadCloudLineIcon,
|
42 |
-
SearchLineIcon,
|
43 |
-
ChevronDownLineIcon,
|
44 |
-
CloseLineIcon,
|
45 |
-
EditLineIcon,
|
46 |
-
DeleteBinLineIcon,
|
47 |
-
DownloadLineIcon,
|
48 |
-
ShareLineIcon,
|
49 |
-
LocationIcon,
|
50 |
-
CheckLineIcon,
|
51 |
-
AlertLineIcon,
|
52 |
-
InfoIcon,
|
53 |
-
} from '@ifrc-go/icons';
|
54 |
-
|
55 |
-
export default function DemoPage() {
|
56 |
-
const [showModal, setShowModal] = useState(false);
|
57 |
-
const [showPopup, setShowPopup] = useState(false);
|
58 |
-
const [loading, setLoading] = useState(false);
|
59 |
-
const [textValue, setTextValue] = useState('');
|
60 |
-
const [selectValue, setSelectValue] = useState('');
|
61 |
-
const [multiSelectValue, setMultiSelectValue] = useState<string[]>([]);
|
62 |
-
const [checkboxValue, setCheckboxValue] = useState(false);
|
63 |
-
const [radioValue, setRadioValue] = useState('option1');
|
64 |
-
const [switchValue, setSwitchValue] = useState(false);
|
65 |
-
const [dateValue, setDateValue] = useState<string>('');
|
66 |
-
const [numberValue, setNumberValue] = useState<number | undefined>();
|
67 |
-
const [passwordValue, setPasswordValue] = useState('');
|
68 |
-
const [booleanValue, setBooleanValue] = useState(false);
|
69 |
-
const [segmentValue, setSegmentValue] = useState('option1');
|
70 |
-
|
71 |
-
|
72 |
-
const dummyOptions = [
|
73 |
-
{ key: 'option1', label: 'Option 1' },
|
74 |
-
{ key: 'option2', label: 'Option 2' },
|
75 |
-
{ key: 'option3', label: 'Option 3' },
|
76 |
-
{ key: 'option4', label: 'Option 4' },
|
77 |
-
];
|
78 |
-
|
79 |
-
const dummyCountries = [
|
80 |
-
{ c_code: 'US', label: 'United States', r_code: 'NAM' },
|
81 |
-
{ c_code: 'CA', label: 'Canada', r_code: 'NAM' },
|
82 |
-
{ c_code: 'MX', label: 'Mexico', r_code: 'NAM' },
|
83 |
-
{ c_code: 'BR', label: 'Brazil', r_code: 'SAM' },
|
84 |
-
{ c_code: 'AR', label: 'Argentina', r_code: 'SAM' },
|
85 |
-
{ c_code: 'UK', label: 'United Kingdom', r_code: 'EUR' },
|
86 |
-
{ c_code: 'DE', label: 'Germany', r_code: 'EUR' },
|
87 |
-
{ c_code: 'FR', label: 'France', r_code: 'EUR' },
|
88 |
-
];
|
89 |
-
|
90 |
-
|
91 |
-
|
92 |
-
const dummyChartData = [
|
93 |
-
{ name: 'Red Cross', value: 45 },
|
94 |
-
{ name: 'UNICEF', value: 30 },
|
95 |
-
{ name: 'WHO', value: 15 },
|
96 |
-
{ name: 'WFP', value: 10 },
|
97 |
-
];
|
98 |
-
|
99 |
-
|
100 |
-
|
101 |
-
const dummyBarData = [
|
102 |
-
{ name: 'Q1', value: 100 },
|
103 |
-
{ name: 'Q2', value: 150 },
|
104 |
-
{ name: 'Q3', value: 120 },
|
105 |
-
{ name: 'Q4', value: 180 },
|
106 |
-
];
|
107 |
-
|
108 |
-
const handleLoading = () => {
|
109 |
-
setLoading(true);
|
110 |
-
setTimeout(() => setLoading(false), 2000);
|
111 |
-
};
|
112 |
-
|
113 |
-
const handleTextChange = (value: string | undefined) => {
|
114 |
-
setTextValue(value || '');
|
115 |
-
};
|
116 |
-
|
117 |
-
const handlePasswordChange = (value: string | undefined) => {
|
118 |
-
setPasswordValue(value || '');
|
119 |
-
};
|
120 |
-
|
121 |
-
const handleNumberChange = (value: number | undefined) => {
|
122 |
-
setNumberValue(value);
|
123 |
-
};
|
124 |
-
|
125 |
-
const handleDateChange = (value: string | undefined) => {
|
126 |
-
setDateValue(value || '');
|
127 |
-
};
|
128 |
-
|
129 |
-
const handleSelectChange = (value: string | undefined) => {
|
130 |
-
setSelectValue(value || '');
|
131 |
-
};
|
132 |
-
|
133 |
-
const handleMultiSelectChange = (value: string[]) => {
|
134 |
-
setMultiSelectValue(value);
|
135 |
-
};
|
136 |
-
|
137 |
-
const handleCheckboxChange = (value: boolean) => {
|
138 |
-
setCheckboxValue(value);
|
139 |
-
};
|
140 |
-
|
141 |
-
const handleRadioChange = (value: string) => {
|
142 |
-
setRadioValue(value);
|
143 |
-
};
|
144 |
-
|
145 |
-
const handleSwitchChange = (value: boolean) => {
|
146 |
-
setSwitchValue(value);
|
147 |
-
};
|
148 |
-
|
149 |
-
const handleBooleanChange = (value: boolean) => {
|
150 |
-
setBooleanValue(value);
|
151 |
-
};
|
152 |
-
|
153 |
-
const handleSegmentChange = (value: string) => {
|
154 |
-
setSegmentValue(value);
|
155 |
-
};
|
156 |
-
|
157 |
-
return (
|
158 |
-
<PageContainer>
|
159 |
-
<div className="space-y-8">
|
160 |
-
{/* Header Section */}
|
161 |
-
<Container heading="Navigation & Header Components" headingLevel={2} withHeaderBorder withInternalPadding>
|
162 |
-
<div className="space-y-6">
|
163 |
-
{/* Navigation Tabs */}
|
164 |
-
<div>
|
165 |
-
<Heading level={3} className="mb-4">Navigation Tab List</Heading>
|
166 |
-
<NavigationTabList variant="primary">
|
167 |
-
<Button name="upload" variant="primary">Upload</Button>
|
168 |
-
<Button name="analytics" variant="secondary">Analytics</Button>
|
169 |
-
<Button name="explore" variant="secondary">Explore</Button>
|
170 |
-
<Button name="help" variant="secondary">Help</Button>
|
171 |
-
</NavigationTabList>
|
172 |
-
</div>
|
173 |
-
|
174 |
-
{/* Top Banner */}
|
175 |
-
<div>
|
176 |
-
<Heading level={3} className="mb-4">Top Banner</Heading>
|
177 |
-
<div className="bg-blue-50 border border-blue-200 rounded-lg p-4">
|
178 |
-
<div className="flex justify-between items-start">
|
179 |
-
<div>
|
180 |
-
<h4 className="font-semibold text-blue-900">Important Notice</h4>
|
181 |
-
<p className="text-blue-700 mt-1">This is a top banner component for important announcements.</p>
|
182 |
-
</div>
|
183 |
-
<Button name="dismiss" variant="secondary" size={1}>
|
184 |
-
Dismiss
|
185 |
-
</Button>
|
186 |
-
</div>
|
187 |
-
</div>
|
188 |
-
</div>
|
189 |
-
|
190 |
-
{/* Breadcrumbs */}
|
191 |
-
<div>
|
192 |
-
<Heading level={3} className="mb-4">Breadcrumbs</Heading>
|
193 |
-
<nav className="flex" aria-label="Breadcrumb">
|
194 |
-
<ol className="flex items-center space-x-2">
|
195 |
-
<li>
|
196 |
-
<a href="/" className="text-gray-500 hover:text-gray-700">Home</a>
|
197 |
-
</li>
|
198 |
-
<li>
|
199 |
-
<span className="mx-2 text-gray-400">/</span>
|
200 |
-
</li>
|
201 |
-
<li>
|
202 |
-
<a href="/analytics" className="text-gray-500 hover:text-gray-700">Analytics</a>
|
203 |
-
</li>
|
204 |
-
<li>
|
205 |
-
<span className="mx-2 text-gray-400">/</span>
|
206 |
-
</li>
|
207 |
-
<li>
|
208 |
-
<span className="text-gray-900">Reports</span>
|
209 |
-
</li>
|
210 |
-
</ol>
|
211 |
-
</nav>
|
212 |
-
</div>
|
213 |
-
</div>
|
214 |
-
</Container>
|
215 |
-
|
216 |
-
{/* Basic Components */}
|
217 |
-
<Container heading="Basic Components" headingLevel={2} withHeaderBorder withInternalPadding>
|
218 |
-
<div className="space-y-6">
|
219 |
-
{/* Buttons */}
|
220 |
-
<div>
|
221 |
-
<Heading level={3} className="mb-4">Buttons</Heading>
|
222 |
-
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
223 |
-
<Button name="primary" variant="primary">Primary Button</Button>
|
224 |
-
<Button name="secondary" variant="secondary">Secondary Button</Button>
|
225 |
-
<Button name="tertiary" variant="tertiary">Tertiary Button</Button>
|
226 |
-
<Button name="disabled" disabled>Disabled Button</Button>
|
227 |
-
<Button name="loading" onClick={handleLoading} disabled={loading}>
|
228 |
-
{loading ? <Spinner /> : 'Loading Button'}
|
229 |
-
</Button>
|
230 |
-
<ConfirmButton name="confirm" onConfirm={() => alert('Confirmed!')}>
|
231 |
-
Confirm Button
|
232 |
-
</ConfirmButton>
|
233 |
-
<Button name="with-icon" variant="primary">
|
234 |
-
<UploadCloudLineIcon className="w-4 h-4 mr-2" />
|
235 |
-
Upload File
|
236 |
-
</Button>
|
237 |
-
</div>
|
238 |
-
</div>
|
239 |
-
|
240 |
-
{/* Icon Buttons */}
|
241 |
-
<div>
|
242 |
-
<Heading level={3} className="mb-4">Icon Buttons</Heading>
|
243 |
-
<div className="grid grid-cols-2 md:grid-cols-4 lg:grid-cols-6 gap-4">
|
244 |
-
<IconButton name="upload" variant="primary" title="Upload" ariaLabel="Upload">
|
245 |
-
<UploadCloudLineIcon />
|
246 |
-
</IconButton>
|
247 |
-
<IconButton name="search" variant="secondary" title="Search" ariaLabel="Search">
|
248 |
-
<SearchLineIcon />
|
249 |
-
</IconButton>
|
250 |
-
<IconButton name="edit" variant="tertiary" title="Edit" ariaLabel="Edit">
|
251 |
-
<EditLineIcon />
|
252 |
-
</IconButton>
|
253 |
-
<IconButton name="delete" variant="tertiary" title="Delete" ariaLabel="Delete">
|
254 |
-
<DeleteBinLineIcon />
|
255 |
-
</IconButton>
|
256 |
-
<IconButton name="download" variant="tertiary" title="Download" ariaLabel="Download">
|
257 |
-
<DownloadLineIcon />
|
258 |
-
</IconButton>
|
259 |
-
<IconButton name="share" variant="tertiary" title="Share" ariaLabel="Share">
|
260 |
-
<ShareLineIcon />
|
261 |
-
</IconButton>
|
262 |
-
</div>
|
263 |
-
</div>
|
264 |
-
|
265 |
-
{/* Chips */}
|
266 |
-
<div>
|
267 |
-
<h3 className="text-lg font-semibold mb-4">Chips</h3>
|
268 |
-
<div className="flex flex-wrap gap-2">
|
269 |
-
<span className="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-red-100 text-red-800">
|
270 |
-
Primary Chip
|
271 |
-
</span>
|
272 |
-
<span className="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-gray-100 text-gray-800">
|
273 |
-
Secondary Chip
|
274 |
-
</span>
|
275 |
-
<span className="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-blue-100 text-blue-800">
|
276 |
-
Tertiary Chip
|
277 |
-
</span>
|
278 |
-
</div>
|
279 |
-
</div>
|
280 |
-
|
281 |
-
{/* Tooltips */}
|
282 |
-
<div>
|
283 |
-
<h3 className="text-lg font-semibold mb-4">Tooltips</h3>
|
284 |
-
<div className="flex gap-4">
|
285 |
-
<div className="relative group">
|
286 |
-
<Button name="tooltip">Hover me</Button>
|
287 |
-
<div className="absolute bottom-full left-1/2 transform -translate-x-1/2 mb-2 px-2 py-1 text-xs text-white bg-gray-900 rounded opacity-0 group-hover:opacity-100 transition-opacity">
|
288 |
-
This is a tooltip
|
289 |
-
</div>
|
290 |
-
</div>
|
291 |
-
<div className="relative group">
|
292 |
-
<IconButton name="tooltip-icon" variant="tertiary" title="Info" ariaLabel="Info">
|
293 |
-
<InfoIcon />
|
294 |
-
</IconButton>
|
295 |
-
<div className="absolute bottom-full left-1/2 transform -translate-x-1/2 mb-2 px-2 py-1 text-xs text-white bg-gray-900 rounded opacity-0 group-hover:opacity-100 transition-opacity">
|
296 |
-
Another tooltip
|
297 |
-
</div>
|
298 |
-
</div>
|
299 |
-
</div>
|
300 |
-
</div>
|
301 |
-
</div>
|
302 |
-
</Container>
|
303 |
-
|
304 |
-
{/* Form Elements */}
|
305 |
-
<Container heading="Form Elements" headingLevel={2} withHeaderBorder withInternalPadding>
|
306 |
-
<div className="space-y-6">
|
307 |
-
{/* Text Inputs */}
|
308 |
-
<div>
|
309 |
-
<h3 className="text-lg font-semibold mb-4">Text Inputs</h3>
|
310 |
-
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
311 |
-
<InputSection>
|
312 |
-
<InputLabel>Text Input</InputLabel>
|
313 |
-
<TextInput
|
314 |
-
name="text"
|
315 |
-
value={textValue}
|
316 |
-
onChange={handleTextChange}
|
317 |
-
placeholder="Enter text..."
|
318 |
-
/>
|
319 |
-
<InputHint>This is a hint text</InputHint>
|
320 |
-
</InputSection>
|
321 |
-
|
322 |
-
<InputSection>
|
323 |
-
<InputLabel>Password Input</InputLabel>
|
324 |
-
<PasswordInput
|
325 |
-
name="password"
|
326 |
-
value={passwordValue}
|
327 |
-
onChange={handlePasswordChange}
|
328 |
-
placeholder="Enter password..."
|
329 |
-
/>
|
330 |
-
</InputSection>
|
331 |
-
|
332 |
-
<InputSection>
|
333 |
-
<InputLabel>Number Input</InputLabel>
|
334 |
-
<NumberInput
|
335 |
-
name="number"
|
336 |
-
value={numberValue}
|
337 |
-
onChange={handleNumberChange}
|
338 |
-
placeholder="Enter number..."
|
339 |
-
/>
|
340 |
-
</InputSection>
|
341 |
-
|
342 |
-
<InputSection>
|
343 |
-
<InputLabel>Date Input</InputLabel>
|
344 |
-
<DateInput
|
345 |
-
name="date"
|
346 |
-
value={dateValue}
|
347 |
-
onChange={handleDateChange}
|
348 |
-
placeholder="Select date..."
|
349 |
-
/>
|
350 |
-
</InputSection>
|
351 |
-
|
352 |
-
<InputSection>
|
353 |
-
<InputLabel>Text Area</InputLabel>
|
354 |
-
<TextArea
|
355 |
-
name="textarea"
|
356 |
-
value=""
|
357 |
-
onChange={() => {}}
|
358 |
-
placeholder="Enter long text..."
|
359 |
-
rows={4}
|
360 |
-
/>
|
361 |
-
</InputSection>
|
362 |
-
|
363 |
-
<InputSection>
|
364 |
-
<InputLabel>File Input</InputLabel>
|
365 |
-
<RawFileInput
|
366 |
-
name="file"
|
367 |
-
accept="image/*"
|
368 |
-
onChange={() => {}}
|
369 |
-
/>
|
370 |
-
</InputSection>
|
371 |
-
</div>
|
372 |
-
</div>
|
373 |
-
|
374 |
-
{/* Select Inputs */}
|
375 |
-
<div>
|
376 |
-
<h3 className="text-lg font-semibold mb-4">Select Inputs</h3>
|
377 |
-
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
378 |
-
<InputSection>
|
379 |
-
<InputLabel>Select Input</InputLabel>
|
380 |
-
<SelectInput
|
381 |
-
name="select"
|
382 |
-
value={selectValue}
|
383 |
-
onChange={handleSelectChange}
|
384 |
-
options={dummyOptions}
|
385 |
-
keySelector={(o) => o.key}
|
386 |
-
labelSelector={(o) => o.label}
|
387 |
-
placeholder="Select an option..."
|
388 |
-
/>
|
389 |
-
</InputSection>
|
390 |
-
|
391 |
-
<InputSection>
|
392 |
-
<InputLabel>Multi Select Input</InputLabel>
|
393 |
-
<MultiSelectInput
|
394 |
-
name="multiselect"
|
395 |
-
value={multiSelectValue}
|
396 |
-
onChange={handleMultiSelectChange}
|
397 |
-
options={dummyCountries}
|
398 |
-
keySelector={(o) => o.c_code}
|
399 |
-
labelSelector={(o) => o.label}
|
400 |
-
placeholder="Select countries..."
|
401 |
-
/>
|
402 |
-
</InputSection>
|
403 |
-
|
404 |
-
<InputSection>
|
405 |
-
<InputLabel>Search Select Input</InputLabel>
|
406 |
-
<SearchSelectInput
|
407 |
-
name="searchselect"
|
408 |
-
value=""
|
409 |
-
onChange={() => {}}
|
410 |
-
options={dummyCountries}
|
411 |
-
keySelector={(o) => o.c_code}
|
412 |
-
labelSelector={(o) => o.label}
|
413 |
-
placeholder="Search countries..."
|
414 |
-
selectedOnTop
|
415 |
-
/>
|
416 |
-
</InputSection>
|
417 |
-
|
418 |
-
<InputSection>
|
419 |
-
<InputLabel>Search Multi Select Input</InputLabel>
|
420 |
-
<SearchMultiSelectInput
|
421 |
-
name="searchmultiselect"
|
422 |
-
value={[]}
|
423 |
-
onChange={() => {}}
|
424 |
-
options={dummyCountries}
|
425 |
-
keySelector={(o) => o.c_code}
|
426 |
-
labelSelector={(o) => o.label}
|
427 |
-
placeholder="Search and select countries..."
|
428 |
-
selectedOnTop
|
429 |
-
/>
|
430 |
-
</InputSection>
|
431 |
-
</div>
|
432 |
-
</div>
|
433 |
-
|
434 |
-
{/* Checkboxes & Radios */}
|
435 |
-
<div>
|
436 |
-
<h3 className="text-lg font-semibold mb-4">Checkboxes & Radios</h3>
|
437 |
-
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
438 |
-
<InputSection>
|
439 |
-
<InputLabel>Checkbox</InputLabel>
|
440 |
-
<Checkbox
|
441 |
-
name="checkbox"
|
442 |
-
value={checkboxValue}
|
443 |
-
onChange={handleCheckboxChange}
|
444 |
-
label="Accept terms and conditions"
|
445 |
-
/>
|
446 |
-
</InputSection>
|
447 |
-
|
448 |
-
<InputSection>
|
449 |
-
<InputLabel>Radio Buttons</InputLabel>
|
450 |
-
<div className="space-y-2">
|
451 |
-
<label className="flex items-center">
|
452 |
-
<input
|
453 |
-
type="radio"
|
454 |
-
name="radio"
|
455 |
-
value="option1"
|
456 |
-
checked={radioValue === 'option1'}
|
457 |
-
onChange={(e) => handleRadioChange(e.target.value)}
|
458 |
-
className="mr-2"
|
459 |
-
/>
|
460 |
-
<span className="text-sm">Option 1</span>
|
461 |
-
</label>
|
462 |
-
<label className="flex items-center">
|
463 |
-
<input
|
464 |
-
type="radio"
|
465 |
-
name="radio"
|
466 |
-
value="option2"
|
467 |
-
checked={radioValue === 'option2'}
|
468 |
-
onChange={(e) => handleRadioChange(e.target.value)}
|
469 |
-
className="mr-2"
|
470 |
-
/>
|
471 |
-
<span className="text-sm">Option 2</span>
|
472 |
-
</label>
|
473 |
-
<label className="flex items-center">
|
474 |
-
<input
|
475 |
-
type="radio"
|
476 |
-
name="radio"
|
477 |
-
value="option3"
|
478 |
-
checked={radioValue === 'option3'}
|
479 |
-
onChange={(e) => handleRadioChange(e.target.value)}
|
480 |
-
className="mr-2"
|
481 |
-
/>
|
482 |
-
<span className="text-sm">Option 3</span>
|
483 |
-
</label>
|
484 |
-
</div>
|
485 |
-
</InputSection>
|
486 |
-
|
487 |
-
<InputSection>
|
488 |
-
<InputLabel>Switch</InputLabel>
|
489 |
-
<Switch
|
490 |
-
name="switch"
|
491 |
-
value={switchValue}
|
492 |
-
onChange={handleSwitchChange}
|
493 |
-
label="Enable notifications"
|
494 |
-
/>
|
495 |
-
</InputSection>
|
496 |
-
|
497 |
-
<InputSection>
|
498 |
-
<InputLabel>Boolean Input</InputLabel>
|
499 |
-
<BooleanInput
|
500 |
-
name="boolean"
|
501 |
-
value={booleanValue}
|
502 |
-
onChange={handleBooleanChange}
|
503 |
-
label="Enable feature"
|
504 |
-
/>
|
505 |
-
</InputSection>
|
506 |
-
</div>
|
507 |
-
</div>
|
508 |
-
|
509 |
-
{/* Segment Input */}
|
510 |
-
<div>
|
511 |
-
<h3 className="text-lg font-semibold mb-4">Segment Input</h3>
|
512 |
-
<InputSection>
|
513 |
-
<InputLabel>Segment Input</InputLabel>
|
514 |
-
<SegmentInput
|
515 |
-
name="segment"
|
516 |
-
value={segmentValue}
|
517 |
-
onChange={handleSegmentChange}
|
518 |
-
options={dummyOptions}
|
519 |
-
keySelector={(o) => o.key}
|
520 |
-
labelSelector={(o) => o.label}
|
521 |
-
/>
|
522 |
-
</InputSection>
|
523 |
-
</div>
|
524 |
-
</div>
|
525 |
-
</Container>
|
526 |
-
|
527 |
-
{/* Data Display */}
|
528 |
-
<Container heading="Data Display" headingLevel={2} withHeaderBorder withInternalPadding>
|
529 |
-
<div className="space-y-6">
|
530 |
-
{/* Key Figures */}
|
531 |
-
<div>
|
532 |
-
<h3 className="text-lg font-semibold mb-4">Key Figures</h3>
|
533 |
-
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
|
534 |
-
<KeyFigure value={1234} label="Total Users" />
|
535 |
-
<KeyFigure value={567} label="Active Projects" />
|
536 |
-
<KeyFigure value={89} label="Countries" />
|
537 |
-
<KeyFigure value={12.5} label="Growth Rate" suffix="%" />
|
538 |
-
</div>
|
539 |
-
</div>
|
540 |
-
|
541 |
-
{/* Charts */}
|
542 |
-
<div>
|
543 |
-
<h3 className="text-lg font-semibold mb-4">Charts</h3>
|
544 |
-
<div className="grid grid-cols-1 lg:grid-cols-2 gap-8">
|
545 |
-
<div>
|
546 |
-
<h4 className="text-md font-semibold mb-4">Pie Chart</h4>
|
547 |
-
<PieChart
|
548 |
-
data={dummyChartData}
|
549 |
-
valueSelector={(d) => d.value}
|
550 |
-
labelSelector={(d) => d.name}
|
551 |
-
keySelector={(d) => d.name}
|
552 |
-
colorSelector={() => '#dc2626'}
|
553 |
-
showPercentageInLegend
|
554 |
-
/>
|
555 |
-
</div>
|
556 |
-
<div>
|
557 |
-
<h4 className="text-md font-semibold mb-4">Bar Chart</h4>
|
558 |
-
<BarChart
|
559 |
-
data={dummyBarData}
|
560 |
-
valueSelector={(d) => d.value}
|
561 |
-
labelSelector={(d) => d.name}
|
562 |
-
keySelector={(d) => d.name}
|
563 |
-
/>
|
564 |
-
</div>
|
565 |
-
</div>
|
566 |
-
<div className="mt-8">
|
567 |
-
<h4 className="text-md font-semibold mb-4">Time Series Chart</h4>
|
568 |
-
<div className="h-64 bg-gray-50 rounded border-2 border-dashed border-gray-400 flex items-center justify-center">
|
569 |
-
<div className="text-center">
|
570 |
-
<div className="w-16 h-16 mx-auto mb-2 bg-gray-200 rounded flex items-center justify-center">
|
571 |
-
<div className="w-8 h-8 bg-gray-400 rounded"></div>
|
572 |
-
</div>
|
573 |
-
<p className="text-gray-600">Time Series Chart Component</p>
|
574 |
-
<p className="text-sm text-gray-500">This would render a time series chart with data points over time</p>
|
575 |
-
</div>
|
576 |
-
</div>
|
577 |
-
</div>
|
578 |
-
</div>
|
579 |
-
|
580 |
-
|
581 |
-
|
582 |
-
{/* Lists */}
|
583 |
-
<div>
|
584 |
-
<h3 className="text-lg font-semibold mb-4">Lists</h3>
|
585 |
-
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
586 |
-
<div>
|
587 |
-
<h4 className="text-md font-semibold mb-4">Basic List</h4>
|
588 |
-
<ul className="space-y-2">
|
589 |
-
{dummyCountries.slice(0, 5).map((country) => (
|
590 |
-
<li key={country.c_code} className="flex items-center justify-between p-2 bg-gray-50 rounded">
|
591 |
-
<span>{country.label}</span>
|
592 |
-
<span className="text-sm text-gray-500">{country.c_code}</span>
|
593 |
-
</li>
|
594 |
-
))}
|
595 |
-
</ul>
|
596 |
-
</div>
|
597 |
-
<div>
|
598 |
-
<h4 className="text-md font-semibold mb-4">Raw List</h4>
|
599 |
-
<ul className="space-y-1">
|
600 |
-
{dummyCountries.slice(0, 5).map((country) => (
|
601 |
-
<li key={country.c_code} className="text-sm">
|
602 |
-
{country.label}
|
603 |
-
</li>
|
604 |
-
))}
|
605 |
-
</ul>
|
606 |
-
</div>
|
607 |
-
</div>
|
608 |
-
</div>
|
609 |
-
|
610 |
-
{/* Output Components */}
|
611 |
-
<div>
|
612 |
-
<h3 className="text-lg font-semibold mb-4">Output Components</h3>
|
613 |
-
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
614 |
-
<div>
|
615 |
-
<h4 className="text-md font-semibold mb-4">Text Output</h4>
|
616 |
-
<TextOutput value="This is some text output" />
|
617 |
-
</div>
|
618 |
-
<div>
|
619 |
-
<h4 className="text-md font-semibold mb-4">Number Output</h4>
|
620 |
-
<NumberOutput value={1234.56} />
|
621 |
-
</div>
|
622 |
-
<div>
|
623 |
-
<h4 className="text-md font-semibold mb-4">Date Output</h4>
|
624 |
-
<DateOutput value={new Date()} />
|
625 |
-
</div>
|
626 |
-
<div>
|
627 |
-
<h4 className="text-md font-semibold mb-4">Boolean Output</h4>
|
628 |
-
<BooleanOutput value={true} />
|
629 |
-
</div>
|
630 |
-
</div>
|
631 |
-
</div>
|
632 |
-
</div>
|
633 |
-
</Container>
|
634 |
-
|
635 |
-
{/* Feedback */}
|
636 |
-
<Container heading="Feedback" headingLevel={2} withHeaderBorder withInternalPadding>
|
637 |
-
<div className="space-y-6">
|
638 |
-
{/* Alerts & Messages */}
|
639 |
-
<div>
|
640 |
-
<h3 className="text-lg font-semibold mb-4">Alerts & Messages</h3>
|
641 |
-
<div className="space-y-4">
|
642 |
-
<div className="bg-green-50 border border-green-200 rounded-md p-4">
|
643 |
-
<div className="flex">
|
644 |
-
<div className="flex-shrink-0">
|
645 |
-
<CheckLineIcon className="h-5 w-5 text-green-400" />
|
646 |
-
</div>
|
647 |
-
<div className="ml-3">
|
648 |
-
<p className="text-sm text-green-800">This is a success alert message.</p>
|
649 |
-
</div>
|
650 |
-
</div>
|
651 |
-
</div>
|
652 |
-
<div className="bg-yellow-50 border border-yellow-200 rounded-md p-4">
|
653 |
-
<div className="flex">
|
654 |
-
<div className="flex-shrink-0">
|
655 |
-
<AlertLineIcon className="h-5 w-5 text-yellow-400" />
|
656 |
-
</div>
|
657 |
-
<div className="ml-3">
|
658 |
-
<p className="text-sm text-yellow-800">This is a warning alert message.</p>
|
659 |
-
</div>
|
660 |
-
</div>
|
661 |
-
</div>
|
662 |
-
<div className="bg-red-50 border border-red-200 rounded-md p-4">
|
663 |
-
<div className="flex">
|
664 |
-
<div className="flex-shrink-0">
|
665 |
-
<AlertLineIcon className="h-5 w-5 text-red-400" />
|
666 |
-
</div>
|
667 |
-
<div className="ml-3">
|
668 |
-
<p className="text-sm text-red-800">This is an error alert message.</p>
|
669 |
-
</div>
|
670 |
-
</div>
|
671 |
-
</div>
|
672 |
-
<div className="bg-blue-50 border border-blue-200 rounded-md p-4">
|
673 |
-
<div className="flex">
|
674 |
-
<div className="flex-shrink-0">
|
675 |
-
<InfoIcon className="h-5 w-5 text-blue-400" />
|
676 |
-
</div>
|
677 |
-
<div className="ml-3">
|
678 |
-
<p className="text-sm text-blue-800">This is an info alert message.</p>
|
679 |
-
</div>
|
680 |
-
</div>
|
681 |
-
</div>
|
682 |
-
<div className="bg-gray-50 border border-gray-200 rounded-md p-4">
|
683 |
-
<div className="flex">
|
684 |
-
<div className="flex-shrink-0">
|
685 |
-
<InfoIcon className="h-5 w-5 text-gray-400" />
|
686 |
-
</div>
|
687 |
-
<div className="ml-3">
|
688 |
-
<h4 className="text-sm font-medium text-gray-800">Information Message</h4>
|
689 |
-
<p className="text-sm text-gray-600 mt-1">This is a message component with a title.</p>
|
690 |
-
</div>
|
691 |
-
</div>
|
692 |
-
</div>
|
693 |
-
</div>
|
694 |
-
</div>
|
695 |
-
|
696 |
-
{/* Progress Bars */}
|
697 |
-
<div>
|
698 |
-
<h3 className="text-lg font-semibold mb-4">Progress Bars</h3>
|
699 |
-
<div className="space-y-4">
|
700 |
-
<div>
|
701 |
-
<h4 className="text-sm font-medium mb-2">Basic Progress Bar</h4>
|
702 |
-
<ProgressBar value={75} totalValue={100} />
|
703 |
-
</div>
|
704 |
-
<div>
|
705 |
-
<h4 className="text-sm font-medium mb-2">Stacked Progress Bar</h4>
|
706 |
-
<StackedProgressBar
|
707 |
-
data={[
|
708 |
-
{ key: 'completed', value: 60, color: '#dc2626' },
|
709 |
-
{ key: 'in-progress', value: 25, color: '#f59e0b' },
|
710 |
-
{ key: 'pending', value: 15, color: '#6b7280' },
|
711 |
-
]}
|
712 |
-
valueSelector={(d) => d.value}
|
713 |
-
labelSelector={(d) => d.key}
|
714 |
-
colorSelector={(d) => d.color}
|
715 |
-
/>
|
716 |
-
</div>
|
717 |
-
</div>
|
718 |
-
</div>
|
719 |
-
|
720 |
-
{/* Loading States */}
|
721 |
-
<div>
|
722 |
-
<h3 className="text-lg font-semibold mb-4">Loading States</h3>
|
723 |
-
<div className="space-y-4">
|
724 |
-
<div>
|
725 |
-
<h4 className="text-sm font-medium mb-2">Spinner</h4>
|
726 |
-
<Spinner />
|
727 |
-
</div>
|
728 |
-
<div>
|
729 |
-
<h4 className="text-sm font-medium mb-2">Block Loading</h4>
|
730 |
-
<BlockLoading />
|
731 |
-
</div>
|
732 |
-
</div>
|
733 |
-
</div>
|
734 |
-
|
735 |
-
{/* Modals & Popups */}
|
736 |
-
<div>
|
737 |
-
<h3 className="text-lg font-semibold mb-4">Modals & Popups</h3>
|
738 |
-
<div className="space-y-4">
|
739 |
-
<Button name="modal" onClick={() => setShowModal(true)}>
|
740 |
-
Open Modal
|
741 |
-
</Button>
|
742 |
-
<Button name="popup" onClick={() => setShowPopup(true)}>
|
743 |
-
Open Popup
|
744 |
-
</Button>
|
745 |
-
<Button name="info-popup" onClick={() => {}}>
|
746 |
-
Info Popup
|
747 |
-
</Button>
|
748 |
-
</div>
|
749 |
-
</div>
|
750 |
-
|
751 |
-
{/* Dropdown Menu */}
|
752 |
-
<div>
|
753 |
-
<h3 className="text-lg font-semibold mb-4">Dropdown Menu</h3>
|
754 |
-
<div className="relative inline-block text-left">
|
755 |
-
<Button name="dropdown">
|
756 |
-
Actions <ChevronDownLineIcon className="w-4 h-4 ml-2" />
|
757 |
-
</Button>
|
758 |
-
<div className="hidden absolute right-0 mt-2 w-48 bg-white rounded-md shadow-lg ring-1 ring-black ring-opacity-5">
|
759 |
-
<div className="py-1">
|
760 |
-
<button className="flex items-center w-full px-4 py-2 text-sm text-gray-700 hover:bg-gray-100">
|
761 |
-
<EditLineIcon className="w-4 h-4 mr-2" />
|
762 |
-
Edit
|
763 |
-
</button>
|
764 |
-
<button className="flex items-center w-full px-4 py-2 text-sm text-gray-700 hover:bg-gray-100">
|
765 |
-
<DeleteBinLineIcon className="w-4 h-4 mr-2" />
|
766 |
-
Delete
|
767 |
-
</button>
|
768 |
-
<button className="flex items-center w-full px-4 py-2 text-sm text-gray-700 hover:bg-gray-100">
|
769 |
-
<DownloadLineIcon className="w-4 h-4 mr-2" />
|
770 |
-
Download
|
771 |
-
</button>
|
772 |
-
</div>
|
773 |
-
</div>
|
774 |
-
</div>
|
775 |
-
</div>
|
776 |
-
</div>
|
777 |
-
</Container>
|
778 |
-
|
779 |
-
{/* Layout */}
|
780 |
-
<Container heading="Layout" headingLevel={2} withHeaderBorder withInternalPadding>
|
781 |
-
<div className="space-y-6">
|
782 |
-
{/* Grid System */}
|
783 |
-
<div>
|
784 |
-
<h3 className="text-lg font-semibold mb-4">Grid System</h3>
|
785 |
-
<div className="grid grid-cols-3 gap-4">
|
786 |
-
<div className="bg-gray-100 p-4 rounded">Grid Item 1</div>
|
787 |
-
<div className="bg-gray-100 p-4 rounded">Grid Item 2</div>
|
788 |
-
<div className="bg-gray-100 p-4 rounded">Grid Item 3</div>
|
789 |
-
<div className="bg-gray-100 p-4 rounded">Grid Item 4</div>
|
790 |
-
<div className="bg-gray-100 p-4 rounded">Grid Item 5</div>
|
791 |
-
<div className="bg-gray-100 p-4 rounded">Grid Item 6</div>
|
792 |
-
</div>
|
793 |
-
</div>
|
794 |
-
|
795 |
-
{/* Expandable Container */}
|
796 |
-
<div>
|
797 |
-
<h3 className="text-lg font-semibold mb-4">Expandable Container</h3>
|
798 |
-
<div className="border border-gray-200 rounded-lg">
|
799 |
-
<button className="w-full px-4 py-3 text-left bg-gray-50 hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-blue-500">
|
800 |
-
<div className="flex items-center justify-between">
|
801 |
-
<span className="font-medium">Expandable Section</span>
|
802 |
-
<ChevronDownLineIcon className="w-5 h-5 text-gray-500" />
|
803 |
-
</div>
|
804 |
-
</button>
|
805 |
-
<div className="px-4 py-3 border-t border-gray-200">
|
806 |
-
<p>This is the content inside the expandable container. It can contain any components or text.</p>
|
807 |
-
<div className="mt-4">
|
808 |
-
<Button name="inside-expandable">Button inside expandable</Button>
|
809 |
-
</div>
|
810 |
-
</div>
|
811 |
-
</div>
|
812 |
-
</div>
|
813 |
-
|
814 |
-
{/* Tabs */}
|
815 |
-
<div>
|
816 |
-
<h3 className="text-lg font-semibold mb-4">Tabs</h3>
|
817 |
-
<div className="border-b border-gray-200">
|
818 |
-
<nav className="-mb-px flex space-x-8">
|
819 |
-
<button className="border-b-2 border-blue-500 py-2 px-1 text-sm font-medium text-blue-600">
|
820 |
-
Tab 1
|
821 |
-
</button>
|
822 |
-
<button className="border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300 py-2 px-1 text-sm font-medium">
|
823 |
-
Tab 2
|
824 |
-
</button>
|
825 |
-
<button className="border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300 py-2 px-1 text-sm font-medium">
|
826 |
-
Tab 3
|
827 |
-
</button>
|
828 |
-
</nav>
|
829 |
-
<div className="mt-4">
|
830 |
-
<p>Content for tab 1</p>
|
831 |
-
</div>
|
832 |
-
</div>
|
833 |
-
</div>
|
834 |
-
|
835 |
-
{/* Pager */}
|
836 |
-
<div>
|
837 |
-
<h3 className="text-lg font-semibold mb-4">Pager</h3>
|
838 |
-
<div className="flex items-center justify-between">
|
839 |
-
<div className="flex-1 flex justify-between sm:hidden">
|
840 |
-
<button className="relative inline-flex items-center px-4 py-2 border border-gray-300 text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50">
|
841 |
-
Previous
|
842 |
-
</button>
|
843 |
-
<button className="ml-3 relative inline-flex items-center px-4 py-2 border border-gray-300 text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50">
|
844 |
-
Next
|
845 |
-
</button>
|
846 |
-
</div>
|
847 |
-
<div className="hidden sm:flex-1 sm:flex sm:items-center sm:justify-between">
|
848 |
-
<div>
|
849 |
-
<p className="text-sm text-gray-700">
|
850 |
-
Showing <span className="font-medium">1</span> to <span className="font-medium">10</span> of <span className="font-medium">97</span> results
|
851 |
-
</p>
|
852 |
-
</div>
|
853 |
-
<div>
|
854 |
-
<nav className="relative z-0 inline-flex rounded-md shadow-sm -space-x-px">
|
855 |
-
<button className="relative inline-flex items-center px-2 py-2 rounded-l-md border border-gray-300 bg-white text-sm font-medium text-gray-500 hover:bg-gray-50">
|
856 |
-
Previous
|
857 |
-
</button>
|
858 |
-
<button className="relative inline-flex items-center px-4 py-2 border border-gray-300 bg-white text-sm font-medium text-gray-700 hover:bg-gray-50">
|
859 |
-
1
|
860 |
-
</button>
|
861 |
-
<button className="relative inline-flex items-center px-4 py-2 border border-gray-300 bg-blue-50 text-sm font-medium text-blue-600 hover:bg-blue-100">
|
862 |
-
2
|
863 |
-
</button>
|
864 |
-
<button className="relative inline-flex items-center px-4 py-2 border border-gray-300 bg-white text-sm font-medium text-gray-700 hover:bg-gray-50">
|
865 |
-
3
|
866 |
-
</button>
|
867 |
-
<button className="relative inline-flex items-center px-2 py-2 rounded-r-md border border-gray-300 bg-white text-sm font-medium text-gray-500 hover:bg-gray-50">
|
868 |
-
Next
|
869 |
-
</button>
|
870 |
-
</nav>
|
871 |
-
</div>
|
872 |
-
</div>
|
873 |
-
</div>
|
874 |
-
</div>
|
875 |
-
</div>
|
876 |
-
</Container>
|
877 |
-
|
878 |
-
{/* Maps Section */}
|
879 |
-
<Container heading="Maps & Geographic Components" headingLevel={2} withHeaderBorder withInternalPadding>
|
880 |
-
<div className="space-y-6">
|
881 |
-
<div>
|
882 |
-
<h3 className="text-lg font-semibold mb-4">Map Container</h3>
|
883 |
-
<div className="h-64 bg-gray-200 rounded border-2 border-dashed border-gray-400 flex items-center justify-center">
|
884 |
-
<div className="text-center">
|
885 |
-
<LocationIcon className="w-12 h-12 mx-auto text-gray-400 mb-2" />
|
886 |
-
<p className="text-gray-600">Map Container Component</p>
|
887 |
-
<p className="text-sm text-gray-500">This would render a map with MapContainer, MapSource, MapLayer components</p>
|
888 |
-
</div>
|
889 |
-
</div>
|
890 |
-
</div>
|
891 |
-
|
892 |
-
<div>
|
893 |
-
<h3 className="text-lg font-semibold mb-4">Map Popup</h3>
|
894 |
-
<div className="bg-white border rounded-lg p-4 shadow-lg max-w-sm">
|
895 |
-
<div className="flex justify-between items-start mb-2">
|
896 |
-
<h4 className="font-semibold">Country Name</h4>
|
897 |
-
<Button name="close" variant="tertiary" size={1}>
|
898 |
-
<CloseLineIcon />
|
899 |
-
</Button>
|
900 |
-
</div>
|
901 |
-
<p className="text-sm text-gray-600">This represents a MapPopup component with country information.</p>
|
902 |
-
</div>
|
903 |
-
</div>
|
904 |
-
</div>
|
905 |
-
</Container>
|
906 |
-
|
907 |
-
{/* Modals */}
|
908 |
-
{showModal && (
|
909 |
-
<div className="fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full z-50">
|
910 |
-
<div className="relative top-20 mx-auto p-5 border w-96 shadow-lg rounded-md bg-white">
|
911 |
-
<div className="mt-3">
|
912 |
-
<h3 className="text-lg font-medium text-gray-900 mb-4">Modal Example</h3>
|
913 |
-
<p className="text-sm text-gray-500">This is a modal dialog. It can contain any content.</p>
|
914 |
-
<div className="mt-4 flex gap-2">
|
915 |
-
<Button name="modal-close" onClick={() => setShowModal(false)}>
|
916 |
-
Close
|
917 |
-
</Button>
|
918 |
-
<Button name="modal-action" variant="secondary">
|
919 |
-
Action
|
920 |
-
</Button>
|
921 |
-
</div>
|
922 |
-
</div>
|
923 |
-
</div>
|
924 |
-
</div>
|
925 |
-
)}
|
926 |
-
|
927 |
-
{showPopup && (
|
928 |
-
<div className="fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full z-50">
|
929 |
-
<div className="relative top-20 mx-auto p-5 border w-96 shadow-lg rounded-md bg-white">
|
930 |
-
<div className="mt-3">
|
931 |
-
<h3 className="text-lg font-medium text-gray-900 mb-4">Popup Example</h3>
|
932 |
-
<p className="text-sm text-gray-500">This is a popup. It's similar to a modal but with different styling.</p>
|
933 |
-
<div className="mt-4">
|
934 |
-
<Button name="popup-close" onClick={() => setShowPopup(false)}>
|
935 |
-
Close
|
936 |
-
</Button>
|
937 |
-
</div>
|
938 |
-
</div>
|
939 |
-
</div>
|
940 |
-
</div>
|
941 |
-
)}
|
942 |
-
|
943 |
-
{/* Footer */}
|
944 |
-
<Footer>
|
945 |
-
<div className="text-center text-gray-600">
|
946 |
-
<p>IFRC GO UI Components Demo Page</p>
|
947 |
-
<p className="text-sm">Built with IFRC GO Design System</p>
|
948 |
-
</div>
|
949 |
-
</Footer>
|
950 |
-
</div>
|
951 |
-
</PageContainer>
|
952 |
-
);
|
953 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
frontend/src/pages/ExplorePage/ExplorePage.tsx
CHANGED
@@ -33,7 +33,6 @@ export default function ExplorePage() {
|
|
33 |
const [view, setView] = useState<'explore' | 'mapDetails'>('explore');
|
34 |
const [captions, setCaptions] = useState<ImageWithCaptionOut[]>([]);
|
35 |
|
36 |
-
// Use shared filter context instead of local state
|
37 |
const {
|
38 |
search, setSearch,
|
39 |
srcFilter, setSrcFilter,
|
@@ -176,7 +175,6 @@ export default function ExplorePage() {
|
|
176 |
}
|
177 |
|
178 |
try {
|
179 |
-
// Create a JSZip instance
|
180 |
const JSZip = (await import('jszip')).default;
|
181 |
const zip = new JSZip();
|
182 |
|
@@ -184,13 +182,11 @@ export default function ExplorePage() {
|
|
184 |
const crisisMaps = images.filter(img => img.image_type === 'crisis_map');
|
185 |
const droneImages = images.filter(img => img.image_type === 'drone_image');
|
186 |
|
187 |
-
// Create crisis_maps dataset
|
188 |
if (crisisMaps.length > 0) {
|
189 |
const crisisFolder = zip.folder('crisis_maps_dataset');
|
190 |
const crisisImagesFolder = crisisFolder?.folder('images');
|
191 |
|
192 |
if (crisisImagesFolder) {
|
193 |
-
// Download crisis map images and add to zip
|
194 |
const crisisImagePromises = crisisMaps.map(async (image, index) => {
|
195 |
try {
|
196 |
const response = await fetch(image.image_url);
|
@@ -212,12 +208,10 @@ export default function ExplorePage() {
|
|
212 |
const successfulCrisisImages = crisisImageResults.filter(result => result.success);
|
213 |
|
214 |
if (mode === 'fine-tuning') {
|
215 |
-
// Create train.jsonl, test.jsonl, and val.jsonl with stratified sampling
|
216 |
const crisisTrainData: any[] = [];
|
217 |
const crisisTestData: any[] = [];
|
218 |
const crisisValData: any[] = [];
|
219 |
|
220 |
-
// Group crisis images by source for stratified sampling
|
221 |
const crisisImagesBySource = new Map<string, any[]>();
|
222 |
successfulCrisisImages.forEach(result => {
|
223 |
const source = result.image.source || 'unknown';
|
@@ -227,17 +221,13 @@ export default function ExplorePage() {
|
|
227 |
crisisImagesBySource.get(source)!.push(result);
|
228 |
});
|
229 |
|
230 |
-
|
231 |
-
crisisImagesBySource.forEach((images, source) => {
|
232 |
const totalImages = images.length;
|
233 |
const trainCount = Math.floor(totalImages * (trainSplit / 100));
|
234 |
const testCount = Math.floor(totalImages * (testSplit / 100));
|
235 |
-
const valCount = totalImages - trainCount - testCount;
|
236 |
|
237 |
-
// Shuffle images within each source group for randomness
|
238 |
const shuffledImages = [...images].sort(() => Math.random() - 0.5);
|
239 |
|
240 |
-
// Add to train set
|
241 |
crisisTrainData.push(...shuffledImages.slice(0, trainCount).map(result => ({
|
242 |
image: `images/${result.fileName}`,
|
243 |
caption: result.image.edited || result.image.generated || '',
|
@@ -252,7 +242,6 @@ export default function ExplorePage() {
|
|
252 |
}
|
253 |
})));
|
254 |
|
255 |
-
// Add to test set
|
256 |
crisisTestData.push(...shuffledImages.slice(trainCount, trainCount + testCount).map(result => ({
|
257 |
image: `images/${result.fileName}`,
|
258 |
caption: result.image.edited || result.image.generated || '',
|
@@ -267,7 +256,6 @@ export default function ExplorePage() {
|
|
267 |
}
|
268 |
})));
|
269 |
|
270 |
-
// Add to validation set
|
271 |
crisisValData.push(...shuffledImages.slice(trainCount + testCount).map(result => ({
|
272 |
image: `images/${result.fileName}`,
|
273 |
caption: result.image.edited || result.image.generated || '',
|
@@ -290,7 +278,6 @@ export default function ExplorePage() {
|
|
290 |
crisisFolder.file('val.jsonl', JSON.stringify(crisisValData, null, 2));
|
291 |
}
|
292 |
} else {
|
293 |
-
// Standard mode: create individual JSON files for each image
|
294 |
successfulCrisisImages.forEach((result, index) => {
|
295 |
const jsonData = {
|
296 |
image: `images/${result.fileName}`,
|
@@ -320,7 +307,6 @@ export default function ExplorePage() {
|
|
320 |
const droneImagesFolder = droneFolder?.folder('images');
|
321 |
|
322 |
if (droneImagesFolder) {
|
323 |
-
// Download drone images and add to zip
|
324 |
const droneImagePromises = droneImages.map(async (image, index) => {
|
325 |
try {
|
326 |
const response = await fetch(image.image_url);
|
@@ -342,12 +328,10 @@ export default function ExplorePage() {
|
|
342 |
const successfulDroneImages = droneImageResults.filter(result => result.success);
|
343 |
|
344 |
if (mode === 'fine-tuning') {
|
345 |
-
// Create train.jsonl, test.jsonl, and val.jsonl with stratified sampling
|
346 |
const droneTrainData: any[] = [];
|
347 |
const droneTestData: any[] = [];
|
348 |
const droneValData: any[] = [];
|
349 |
|
350 |
-
// Group drone images by event type for stratified sampling
|
351 |
const droneImagesByEventType = new Map<string, any[]>();
|
352 |
successfulDroneImages.forEach(result => {
|
353 |
const eventType = result.image.event_type || 'unknown';
|
@@ -357,17 +341,13 @@ export default function ExplorePage() {
|
|
357 |
droneImagesByEventType.get(eventType)!.push(result);
|
358 |
});
|
359 |
|
360 |
-
|
361 |
-
droneImagesByEventType.forEach((images, eventType) => {
|
362 |
const totalImages = images.length;
|
363 |
const trainCount = Math.floor(totalImages * (trainSplit / 100));
|
364 |
const testCount = Math.floor(totalImages * (testSplit / 100));
|
365 |
-
const valCount = totalImages - trainCount - testCount;
|
366 |
|
367 |
-
// Shuffle images within each event type group for randomness
|
368 |
const shuffledImages = [...images].sort(() => Math.random() - 0.5);
|
369 |
|
370 |
-
// Add to train set
|
371 |
droneTrainData.push(...shuffledImages.slice(0, trainCount).map(result => ({
|
372 |
image: `images/${result.fileName}`,
|
373 |
caption: result.image.edited || result.image.generated || '',
|
@@ -382,7 +362,6 @@ export default function ExplorePage() {
|
|
382 |
}
|
383 |
})));
|
384 |
|
385 |
-
// Add to test set
|
386 |
droneTestData.push(...shuffledImages.slice(trainCount, trainCount + testCount).map(result => ({
|
387 |
image: `images/${result.fileName}`,
|
388 |
caption: result.image.edited || result.image.generated || '',
|
@@ -397,7 +376,6 @@ export default function ExplorePage() {
|
|
397 |
}
|
398 |
})));
|
399 |
|
400 |
-
// Add to validation set
|
401 |
droneValData.push(...shuffledImages.slice(trainCount + testCount).map(result => ({
|
402 |
image: `images/${result.fileName}`,
|
403 |
caption: result.image.edited || result.image.generated || '',
|
@@ -413,14 +391,12 @@ export default function ExplorePage() {
|
|
413 |
})));
|
414 |
});
|
415 |
|
416 |
-
// Add JSONL files to drone folder
|
417 |
if (droneFolder) {
|
418 |
droneFolder.file('train.jsonl', JSON.stringify(droneTrainData, null, 2));
|
419 |
droneFolder.file('test.jsonl', JSON.stringify(droneTestData, null, 2));
|
420 |
droneFolder.file('val.jsonl', JSON.stringify(droneValData, null, 2));
|
421 |
}
|
422 |
} else {
|
423 |
-
// Standard mode: create individual JSON files for each image
|
424 |
successfulDroneImages.forEach((result, index) => {
|
425 |
const jsonData = {
|
426 |
image: `images/${result.fileName}`,
|
@@ -444,7 +420,6 @@ export default function ExplorePage() {
|
|
444 |
}
|
445 |
}
|
446 |
|
447 |
-
// Generate and download zip
|
448 |
const zipBlob = await zip.generateAsync({ type: 'blob' });
|
449 |
const url = URL.createObjectURL(zipBlob);
|
450 |
const link = document.createElement('a');
|
@@ -489,15 +464,14 @@ export default function ExplorePage() {
|
|
489 |
<Button
|
490 |
name="export-dataset"
|
491 |
variant="secondary"
|
492 |
-
|
493 |
-
|
494 |
-
|
495 |
-
|
496 |
-
|
497 |
-
|
498 |
-
|
499 |
-
|
500 |
-
}}
|
501 |
>
|
502 |
Export Dataset
|
503 |
</Button>
|
@@ -834,7 +808,6 @@ export default function ExplorePage() {
|
|
834 |
const remaining = 100 - newTrain;
|
835 |
if (remaining >= 0) {
|
836 |
setTrainSplit(newTrain);
|
837 |
-
// Distribute remaining between test and val
|
838 |
if (testSplit + valSplit > remaining) {
|
839 |
setTestSplit(Math.floor(remaining / 2));
|
840 |
setValSplit(remaining - Math.floor(remaining / 2));
|
@@ -900,7 +873,7 @@ export default function ExplorePage() {
|
|
900 |
name="crisis-maps"
|
901 |
label={`Crisis Maps (${filtered.filter(img => img.image_type === 'crisis_map').length} images)`}
|
902 |
value={crisisMapsSelected}
|
903 |
-
onChange={(value,
|
904 |
disabled={isLoadingFilters}
|
905 |
/>
|
906 |
</div>
|
@@ -910,7 +883,7 @@ export default function ExplorePage() {
|
|
910 |
name="drone-images"
|
911 |
label={`Drone Images (${filtered.filter(img => img.image_type === 'drone_image').length} images)`}
|
912 |
value={droneImagesSelected}
|
913 |
-
onChange={(value,
|
914 |
disabled={isLoadingFilters}
|
915 |
/>
|
916 |
</div>
|
|
|
33 |
const [view, setView] = useState<'explore' | 'mapDetails'>('explore');
|
34 |
const [captions, setCaptions] = useState<ImageWithCaptionOut[]>([]);
|
35 |
|
|
|
36 |
const {
|
37 |
search, setSearch,
|
38 |
srcFilter, setSrcFilter,
|
|
|
175 |
}
|
176 |
|
177 |
try {
|
|
|
178 |
const JSZip = (await import('jszip')).default;
|
179 |
const zip = new JSZip();
|
180 |
|
|
|
182 |
const crisisMaps = images.filter(img => img.image_type === 'crisis_map');
|
183 |
const droneImages = images.filter(img => img.image_type === 'drone_image');
|
184 |
|
|
|
185 |
if (crisisMaps.length > 0) {
|
186 |
const crisisFolder = zip.folder('crisis_maps_dataset');
|
187 |
const crisisImagesFolder = crisisFolder?.folder('images');
|
188 |
|
189 |
if (crisisImagesFolder) {
|
|
|
190 |
const crisisImagePromises = crisisMaps.map(async (image, index) => {
|
191 |
try {
|
192 |
const response = await fetch(image.image_url);
|
|
|
208 |
const successfulCrisisImages = crisisImageResults.filter(result => result.success);
|
209 |
|
210 |
if (mode === 'fine-tuning') {
|
|
|
211 |
const crisisTrainData: any[] = [];
|
212 |
const crisisTestData: any[] = [];
|
213 |
const crisisValData: any[] = [];
|
214 |
|
|
|
215 |
const crisisImagesBySource = new Map<string, any[]>();
|
216 |
successfulCrisisImages.forEach(result => {
|
217 |
const source = result.image.source || 'unknown';
|
|
|
221 |
crisisImagesBySource.get(source)!.push(result);
|
222 |
});
|
223 |
|
224 |
+
crisisImagesBySource.forEach((images, _source) => {
|
|
|
225 |
const totalImages = images.length;
|
226 |
const trainCount = Math.floor(totalImages * (trainSplit / 100));
|
227 |
const testCount = Math.floor(totalImages * (testSplit / 100));
|
|
|
228 |
|
|
|
229 |
const shuffledImages = [...images].sort(() => Math.random() - 0.5);
|
230 |
|
|
|
231 |
crisisTrainData.push(...shuffledImages.slice(0, trainCount).map(result => ({
|
232 |
image: `images/${result.fileName}`,
|
233 |
caption: result.image.edited || result.image.generated || '',
|
|
|
242 |
}
|
243 |
})));
|
244 |
|
|
|
245 |
crisisTestData.push(...shuffledImages.slice(trainCount, trainCount + testCount).map(result => ({
|
246 |
image: `images/${result.fileName}`,
|
247 |
caption: result.image.edited || result.image.generated || '',
|
|
|
256 |
}
|
257 |
})));
|
258 |
|
|
|
259 |
crisisValData.push(...shuffledImages.slice(trainCount + testCount).map(result => ({
|
260 |
image: `images/${result.fileName}`,
|
261 |
caption: result.image.edited || result.image.generated || '',
|
|
|
278 |
crisisFolder.file('val.jsonl', JSON.stringify(crisisValData, null, 2));
|
279 |
}
|
280 |
} else {
|
|
|
281 |
successfulCrisisImages.forEach((result, index) => {
|
282 |
const jsonData = {
|
283 |
image: `images/${result.fileName}`,
|
|
|
307 |
const droneImagesFolder = droneFolder?.folder('images');
|
308 |
|
309 |
if (droneImagesFolder) {
|
|
|
310 |
const droneImagePromises = droneImages.map(async (image, index) => {
|
311 |
try {
|
312 |
const response = await fetch(image.image_url);
|
|
|
328 |
const successfulDroneImages = droneImageResults.filter(result => result.success);
|
329 |
|
330 |
if (mode === 'fine-tuning') {
|
|
|
331 |
const droneTrainData: any[] = [];
|
332 |
const droneTestData: any[] = [];
|
333 |
const droneValData: any[] = [];
|
334 |
|
|
|
335 |
const droneImagesByEventType = new Map<string, any[]>();
|
336 |
successfulDroneImages.forEach(result => {
|
337 |
const eventType = result.image.event_type || 'unknown';
|
|
|
341 |
droneImagesByEventType.get(eventType)!.push(result);
|
342 |
});
|
343 |
|
344 |
+
droneImagesByEventType.forEach((images, _eventType) => {
|
|
|
345 |
const totalImages = images.length;
|
346 |
const trainCount = Math.floor(totalImages * (trainSplit / 100));
|
347 |
const testCount = Math.floor(totalImages * (testSplit / 100));
|
|
|
348 |
|
|
|
349 |
const shuffledImages = [...images].sort(() => Math.random() - 0.5);
|
350 |
|
|
|
351 |
droneTrainData.push(...shuffledImages.slice(0, trainCount).map(result => ({
|
352 |
image: `images/${result.fileName}`,
|
353 |
caption: result.image.edited || result.image.generated || '',
|
|
|
362 |
}
|
363 |
})));
|
364 |
|
|
|
365 |
droneTestData.push(...shuffledImages.slice(trainCount, trainCount + testCount).map(result => ({
|
366 |
image: `images/${result.fileName}`,
|
367 |
caption: result.image.edited || result.image.generated || '',
|
|
|
376 |
}
|
377 |
})));
|
378 |
|
|
|
379 |
droneValData.push(...shuffledImages.slice(trainCount + testCount).map(result => ({
|
380 |
image: `images/${result.fileName}`,
|
381 |
caption: result.image.edited || result.image.generated || '',
|
|
|
391 |
})));
|
392 |
});
|
393 |
|
|
|
394 |
if (droneFolder) {
|
395 |
droneFolder.file('train.jsonl', JSON.stringify(droneTrainData, null, 2));
|
396 |
droneFolder.file('test.jsonl', JSON.stringify(droneTestData, null, 2));
|
397 |
droneFolder.file('val.jsonl', JSON.stringify(droneValData, null, 2));
|
398 |
}
|
399 |
} else {
|
|
|
400 |
successfulDroneImages.forEach((result, index) => {
|
401 |
const jsonData = {
|
402 |
image: `images/${result.fileName}`,
|
|
|
420 |
}
|
421 |
}
|
422 |
|
|
|
423 |
const zipBlob = await zip.generateAsync({ type: 'blob' });
|
424 |
const url = URL.createObjectURL(zipBlob);
|
425 |
const link = document.createElement('a');
|
|
|
464 |
<Button
|
465 |
name="export-dataset"
|
466 |
variant="secondary"
|
467 |
+
onClick={() => {
|
468 |
+
setShowExportModal(true);
|
469 |
+
if (search || srcFilter || catFilter || regionFilter || countryFilter || imageTypeFilter || showReferenceExamples) {
|
470 |
+
setExportModalStage('filters');
|
471 |
+
} else {
|
472 |
+
setExportModalStage('export');
|
473 |
+
}
|
474 |
+
}}
|
|
|
475 |
>
|
476 |
Export Dataset
|
477 |
</Button>
|
|
|
808 |
const remaining = 100 - newTrain;
|
809 |
if (remaining >= 0) {
|
810 |
setTrainSplit(newTrain);
|
|
|
811 |
if (testSplit + valSplit > remaining) {
|
812 |
setTestSplit(Math.floor(remaining / 2));
|
813 |
setValSplit(remaining - Math.floor(remaining / 2));
|
|
|
873 |
name="crisis-maps"
|
874 |
label={`Crisis Maps (${filtered.filter(img => img.image_type === 'crisis_map').length} images)`}
|
875 |
value={crisisMapsSelected}
|
876 |
+
onChange={(value, _name) => setCrisisMapsSelected(value)}
|
877 |
disabled={isLoadingFilters}
|
878 |
/>
|
879 |
</div>
|
|
|
883 |
name="drone-images"
|
884 |
label={`Drone Images (${filtered.filter(img => img.image_type === 'drone_image').length} images)`}
|
885 |
value={droneImagesSelected}
|
886 |
+
onChange={(value, _name) => setDroneImagesSelected(value)}
|
887 |
disabled={isLoadingFilters}
|
888 |
/>
|
889 |
</div>
|
frontend/src/pages/MapDetailsPage/MapDetailPage.tsx
CHANGED
@@ -53,7 +53,6 @@ export default function MapDetailPage() {
|
|
53 |
const [hasNext, setHasNext] = useState(false);
|
54 |
const [isNavigating, setIsNavigating] = useState(false);
|
55 |
|
56 |
-
// Add delete confirmation state
|
57 |
const [showDeleteConfirm, setShowDeleteConfirm] = useState(false);
|
58 |
const [showExportModal, setShowExportModal] = useState(false);
|
59 |
const [exportMode, setExportMode] = useState<'standard' | 'fine-tuning'>('standard');
|
@@ -63,10 +62,8 @@ export default function MapDetailPage() {
|
|
63 |
const [crisisMapsSelected, setCrisisMapsSelected] = useState(true);
|
64 |
const [droneImagesSelected, setDroneImagesSelected] = useState(true);
|
65 |
|
66 |
-
// Add flag to prevent auto-navigation during delete operations
|
67 |
const [isDeleting, setIsDeleting] = useState(false);
|
68 |
|
69 |
-
// Use shared filter context instead of local state
|
70 |
const {
|
71 |
search, setSearch,
|
72 |
srcFilter, setSrcFilter,
|
@@ -116,9 +113,8 @@ export default function MapDetailPage() {
|
|
116 |
|
117 |
// Auto-navigate to first matching item when filters change
|
118 |
useEffect(() => {
|
119 |
-
if (!map || loading || isDeleting) return;
|
120 |
|
121 |
-
// Check if current map matches current filters
|
122 |
const currentMapMatches = () => {
|
123 |
const matchesSearch = !search ||
|
124 |
map.title?.toLowerCase().includes(search.toLowerCase()) ||
|
@@ -176,7 +172,6 @@ export default function MapDetailPage() {
|
|
176 |
if (response.ok) {
|
177 |
const images = await response.json();
|
178 |
|
179 |
-
// Filter images based on current filter criteria
|
180 |
const filteredImages = images.filter((img: any) => {
|
181 |
const matchesSearch = !search ||
|
182 |
img.title?.toLowerCase().includes(search.toLowerCase()) ||
|
@@ -209,45 +204,54 @@ export default function MapDetailPage() {
|
|
209 |
const navigateToItem = async (direction: 'previous' | 'next') => {
|
210 |
if (isNavigating) return;
|
211 |
|
|
|
212 |
try {
|
213 |
const response = await fetch('/api/images');
|
214 |
-
if (
|
215 |
-
|
216 |
-
const images = await response.json();
|
217 |
-
|
218 |
-
// Filter images based on current filter criteria
|
219 |
-
const filteredImages = images.filter((img: any) => {
|
220 |
-
const matchesSearch = !search ||
|
221 |
-
img.title?.toLowerCase().includes(search.toLowerCase()) ||
|
222 |
-
img.generated?.toLowerCase().includes(search.toLowerCase()) ||
|
223 |
-
img.source?.toLowerCase().includes(search.toLowerCase()) ||
|
224 |
-
img.event_type?.toLowerCase().includes(search.toLowerCase());
|
225 |
|
226 |
-
const
|
227 |
-
|
228 |
-
|
229 |
-
|
230 |
-
|
231 |
-
|
232 |
-
|
233 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
234 |
|
235 |
-
|
236 |
-
|
237 |
-
|
238 |
-
|
239 |
-
|
240 |
-
|
241 |
-
|
242 |
-
targetIndex
|
243 |
-
|
244 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
245 |
}
|
246 |
-
|
247 |
-
const targetId = filteredImages[targetIndex].image_id;
|
248 |
-
navigate(`/map/${targetId}`);
|
249 |
} catch (error) {
|
250 |
-
console.error('
|
|
|
|
|
251 |
}
|
252 |
};
|
253 |
|
@@ -269,7 +273,7 @@ export default function MapDetailPage() {
|
|
269 |
|
270 |
const [isGenerating, setIsGenerating] = useState(false);
|
271 |
|
272 |
-
//
|
273 |
const handleDelete = async () => {
|
274 |
if (!map) return;
|
275 |
|
@@ -291,7 +295,6 @@ export default function MapDetailPage() {
|
|
291 |
});
|
292 |
|
293 |
if (response.ok) {
|
294 |
-
// Update local state
|
295 |
setMap(prev => prev ? { ...prev, starred: !prev.starred } : null);
|
296 |
} else {
|
297 |
console.error('Failed to toggle starred status');
|
@@ -304,101 +307,82 @@ export default function MapDetailPage() {
|
|
304 |
const confirmDelete = async () => {
|
305 |
if (!map) return;
|
306 |
|
307 |
-
setIsDeleting(true);
|
308 |
try {
|
309 |
console.log('Deleting image with ID:', map.image_id);
|
310 |
-
const
|
311 |
-
method:
|
312 |
});
|
313 |
|
314 |
-
if (
|
315 |
-
|
316 |
-
|
317 |
-
|
318 |
-
|
319 |
-
|
320 |
-
|
321 |
-
|
322 |
-
try {
|
323 |
-
const response = await fetch('/api/images');
|
324 |
-
if (response.ok) {
|
325 |
-
const images = await response.json();
|
326 |
-
|
327 |
-
// Filter images based on current filter criteria (same logic as navigateToItem)
|
328 |
-
const filteredImages = images.filter((img: any) => {
|
329 |
-
const matchesSearch = !search ||
|
330 |
-
img.title?.toLowerCase().includes(search.toLowerCase()) ||
|
331 |
-
img.generated?.toLowerCase().includes(search.toLowerCase()) ||
|
332 |
-
img.source?.toLowerCase().includes(search.toLowerCase()) ||
|
333 |
-
img.event_type?.toLowerCase().includes(search.toLowerCase());
|
334 |
-
|
335 |
-
const matchesSource = !srcFilter || img.source === srcFilter;
|
336 |
-
const matchesCategory = !catFilter || img.event_type === catFilter;
|
337 |
-
const matchesRegion = !regionFilter ||
|
338 |
-
img.countries?.some((country: any) => country.r_code === regionFilter);
|
339 |
-
const matchesCountry = !countryFilter ||
|
340 |
-
img.countries?.some((country: any) => country.c_code === countryFilter);
|
341 |
-
const matchesImageType = !imageTypeFilter || img.image_type === imageTypeFilter;
|
342 |
-
const matchesReferenceExamples = !showReferenceExamples || img.starred === true;
|
343 |
-
|
344 |
-
return matchesSearch && matchesSource && matchesCategory && matchesRegion && matchesCountry && matchesImageType && matchesReferenceExamples;
|
345 |
-
});
|
346 |
-
|
347 |
-
// Remove the current item from the filtered list since it was deleted
|
348 |
-
const remainingImages = filteredImages.filter((img: any) => img.image_id !== map.image_id);
|
349 |
-
|
350 |
-
console.log('Delete navigation debug:', {
|
351 |
-
originalFilteredCount: filteredImages.length,
|
352 |
-
remainingCount: remainingImages.length,
|
353 |
-
currentIndex: filteredImages.findIndex((img: any) => img.image_id === map.image_id),
|
354 |
-
remainingImages: remainingImages.map((img: any) => ({ id: img.image_id, title: img.title }))
|
355 |
-
});
|
356 |
-
|
357 |
-
if (remainingImages.length > 0) {
|
358 |
-
// Find the current item's position in the original filtered list
|
359 |
-
const currentIndex = filteredImages.findIndex((img: any) => img.image_id === map.image_id);
|
360 |
|
361 |
-
|
362 |
-
|
363 |
-
|
364 |
-
|
365 |
-
|
366 |
-
|
367 |
-
|
368 |
-
|
369 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
370 |
|
371 |
-
|
372 |
|
373 |
-
|
374 |
-
|
375 |
-
|
376 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
377 |
} else {
|
378 |
-
|
379 |
-
|
380 |
-
navigate(`/map/${remainingImages[0].image_id}`);
|
381 |
}
|
382 |
} else {
|
383 |
-
// No more items in filtered list, go to explore page
|
384 |
-
console.log('No remaining items, going to explore page');
|
385 |
navigate('/explore');
|
386 |
}
|
387 |
-
}
|
388 |
-
|
389 |
navigate('/explore');
|
|
|
|
|
390 |
}
|
391 |
-
}
|
392 |
-
console.error('
|
393 |
-
|
394 |
-
navigate('/explore');
|
395 |
-
} finally {
|
396 |
-
setIsDeleting(false); // Reset flag
|
397 |
}
|
398 |
} catch (error) {
|
399 |
console.error('Delete failed:', error);
|
400 |
-
|
401 |
-
setShowDeleteConfirm(false);
|
402 |
}
|
403 |
};
|
404 |
|
@@ -481,7 +465,6 @@ export default function MapDetailPage() {
|
|
481 |
}
|
482 |
};
|
483 |
|
484 |
-
// Helper function to create image data
|
485 |
const createImageData = (map: any, fileName: string) => ({
|
486 |
image: `images/${fileName}`,
|
487 |
caption: map.edited || map.generated || '',
|
@@ -497,23 +480,17 @@ export default function MapDetailPage() {
|
|
497 |
});
|
498 |
|
499 |
const exportDataset = async (mode: 'standard' | 'fine-tuning') => {
|
500 |
-
if (!map)
|
501 |
-
|
502 |
-
return;
|
503 |
-
}
|
504 |
-
|
505 |
try {
|
506 |
-
// Create a JSZip instance
|
507 |
const JSZip = (await import('jszip')).default;
|
508 |
const zip = new JSZip();
|
509 |
|
510 |
-
// Determine which dataset to create based on image type
|
511 |
if (map.image_type === 'crisis_map') {
|
512 |
const crisisFolder = zip.folder('crisis_maps_dataset');
|
513 |
const crisisImagesFolder = crisisFolder?.folder('images');
|
514 |
|
515 |
if (crisisImagesFolder) {
|
516 |
-
// Download the current crisis map image and add to zip
|
517 |
try {
|
518 |
const response = await fetch(map.image_url);
|
519 |
if (!response.ok) throw new Error(`Failed to fetch image ${map.image_id}`);
|
@@ -525,17 +502,11 @@ export default function MapDetailPage() {
|
|
525 |
crisisImagesFolder.file(fileName, blob);
|
526 |
|
527 |
if (mode === 'fine-tuning') {
|
528 |
-
// Create train.jsonl, test.jsonl, and val.jsonl with stratified sampling
|
529 |
const trainData: any[] = [];
|
530 |
const testData: any[] = [];
|
531 |
const valData: any[] = [];
|
532 |
|
533 |
if (String(map?.image_type) === 'crisis_map') {
|
534 |
-
// For crisis maps, group by source
|
535 |
-
const source = map.source || 'unknown';
|
536 |
-
const totalImages = 1; // Only one image in MapDetailPage
|
537 |
-
|
538 |
-
// Since we only have one image, distribute it based on the split
|
539 |
const random = Math.random();
|
540 |
if (random < trainSplit / 100) {
|
541 |
trainData.push(createImageData(map, '0001'));
|
@@ -545,11 +516,6 @@ export default function MapDetailPage() {
|
|
545 |
valData.push(createImageData(map, '0001'));
|
546 |
}
|
547 |
} else if (String(map?.image_type) === 'drone_image') {
|
548 |
-
// For drone images, group by event type
|
549 |
-
const eventType = map.event_type || 'unknown';
|
550 |
-
const totalImages = 1; // Only one image in MapDetailPage
|
551 |
-
|
552 |
-
// Since we only have one image, distribute it based on the split
|
553 |
const random = Math.random();
|
554 |
if (random < trainSplit / 100) {
|
555 |
trainData.push(createImageData(map, '0001'));
|
@@ -560,14 +526,12 @@ export default function MapDetailPage() {
|
|
560 |
}
|
561 |
}
|
562 |
|
563 |
-
// Add JSONL files to dataset folder
|
564 |
if (crisisFolder) {
|
565 |
crisisFolder.file('train.jsonl', JSON.stringify(trainData, null, 2));
|
566 |
crisisFolder.file('test.jsonl', JSON.stringify(testData, null, 2));
|
567 |
crisisFolder.file('val.jsonl', JSON.stringify(valData, null, 2));
|
568 |
}
|
569 |
} else {
|
570 |
-
// Standard mode: create individual JSON file
|
571 |
const jsonData = {
|
572 |
image: `images/${fileName}`,
|
573 |
caption: map.edited || map.generated || '',
|
@@ -596,7 +560,6 @@ export default function MapDetailPage() {
|
|
596 |
const droneImagesFolder = droneFolder?.folder('images');
|
597 |
|
598 |
if (droneImagesFolder) {
|
599 |
-
// Download the current drone image and add to zip
|
600 |
try {
|
601 |
const response = await fetch(map.image_url);
|
602 |
if (!response.ok) throw new Error(`Failed to fetch image ${map.image_id}`);
|
@@ -608,17 +571,11 @@ export default function MapDetailPage() {
|
|
608 |
droneImagesFolder.file(fileName, blob);
|
609 |
|
610 |
if (mode === 'fine-tuning') {
|
611 |
-
// Create train.jsonl, test.jsonl, and val.jsonl with stratified sampling
|
612 |
const trainData: any[] = [];
|
613 |
const testData: any[] = [];
|
614 |
const valData: any[] = [];
|
615 |
|
616 |
if (String(map?.image_type) === 'crisis_map') {
|
617 |
-
// For crisis maps, group by source
|
618 |
-
const source = map.source || 'unknown';
|
619 |
-
const totalImages = 1; // Only one image in MapDetailPage
|
620 |
-
|
621 |
-
// Since we only have one image, distribute it based on the split
|
622 |
const random = Math.random();
|
623 |
if (random < trainSplit / 100) {
|
624 |
trainData.push(createImageData(map, '0001'));
|
@@ -628,11 +585,6 @@ export default function MapDetailPage() {
|
|
628 |
valData.push(createImageData(map, '0001'));
|
629 |
}
|
630 |
} else if (String(map?.image_type) === 'drone_image') {
|
631 |
-
// For drone images, group by event type
|
632 |
-
const eventType = map.event_type || 'unknown';
|
633 |
-
const totalImages = 1; // Only one image in MapDetailPage
|
634 |
-
|
635 |
-
// Since we only have one image, distribute it based on the split
|
636 |
const random = Math.random();
|
637 |
if (random < trainSplit / 100) {
|
638 |
trainData.push(createImageData(map, '0001'));
|
@@ -643,14 +595,12 @@ export default function MapDetailPage() {
|
|
643 |
}
|
644 |
}
|
645 |
|
646 |
-
// Add JSONL files to dataset folder
|
647 |
if (droneFolder) {
|
648 |
droneFolder.file('train.jsonl', JSON.stringify(trainData, null, 2));
|
649 |
droneFolder.file('test.jsonl', JSON.stringify(testData, null, 2));
|
650 |
droneFolder.file('val.jsonl', JSON.stringify(valData, null, 2));
|
651 |
}
|
652 |
} else {
|
653 |
-
// Standard mode: create individual JSON file
|
654 |
const jsonData = {
|
655 |
image: `images/${fileName}`,
|
656 |
caption: map.edited || map.generated || '',
|
@@ -675,7 +625,6 @@ export default function MapDetailPage() {
|
|
675 |
}
|
676 |
}
|
677 |
} else {
|
678 |
-
// For other image types, create a generic dataset
|
679 |
const genericFolder = zip.folder('generic_dataset');
|
680 |
const genericImagesFolder = genericFolder?.folder('images');
|
681 |
|
@@ -691,17 +640,11 @@ export default function MapDetailPage() {
|
|
691 |
genericImagesFolder.file(fileName, blob);
|
692 |
|
693 |
if (mode === 'fine-tuning') {
|
694 |
-
// Create train.jsonl, test.jsonl, and val.jsonl with stratified sampling
|
695 |
const trainData: any[] = [];
|
696 |
const testData: any[] = [];
|
697 |
const valData: any[] = [];
|
698 |
|
699 |
if (String(map?.image_type) === 'crisis_map') {
|
700 |
-
// For crisis maps, group by source
|
701 |
-
const source = map.source || 'unknown';
|
702 |
-
const totalImages = 1; // Only one image in MapDetailPage
|
703 |
-
|
704 |
-
// Since we only have one image, distribute it based on the split
|
705 |
const random = Math.random();
|
706 |
if (random < trainSplit / 100) {
|
707 |
trainData.push(createImageData(map, '0001'));
|
@@ -711,11 +654,6 @@ export default function MapDetailPage() {
|
|
711 |
valData.push(createImageData(map, '0001'));
|
712 |
}
|
713 |
} else if (String(map?.image_type) === 'drone_image') {
|
714 |
-
// For drone images, group by event type
|
715 |
-
const eventType = map.event_type || 'unknown';
|
716 |
-
const totalImages = 1; // Only one image in MapDetailPage
|
717 |
-
|
718 |
-
// Since we only have one image, distribute it based on the split
|
719 |
const random = Math.random();
|
720 |
if (random < trainSplit / 100) {
|
721 |
trainData.push(createImageData(map, '0001'));
|
@@ -726,14 +664,12 @@ export default function MapDetailPage() {
|
|
726 |
}
|
727 |
}
|
728 |
|
729 |
-
// Add JSONL files to dataset folder
|
730 |
if (genericFolder) {
|
731 |
genericFolder.file('train.jsonl', JSON.stringify(trainData, null, 2));
|
732 |
genericFolder.file('test.jsonl', JSON.stringify(testData, null, 2));
|
733 |
genericFolder.file('val.jsonl', JSON.stringify(valData, null, 2));
|
734 |
}
|
735 |
} else {
|
736 |
-
// Standard mode: create individual JSON file
|
737 |
const jsonData = {
|
738 |
image: `images/${fileName}`,
|
739 |
caption: map.edited || map.generated || '',
|
@@ -1241,7 +1177,6 @@ export default function MapDetailPage() {
|
|
1241 |
const remaining = 100 - newTrain;
|
1242 |
if (remaining >= 0) {
|
1243 |
setTrainSplit(newTrain);
|
1244 |
-
// Distribute remaining between test and val
|
1245 |
if (testSplit + valSplit > remaining) {
|
1246 |
setTestSplit(Math.floor(remaining / 2));
|
1247 |
setValSplit(remaining - Math.floor(remaining / 2));
|
@@ -1307,7 +1242,7 @@ export default function MapDetailPage() {
|
|
1307 |
name="crisis-maps"
|
1308 |
label="Crisis Maps"
|
1309 |
value={crisisMapsSelected}
|
1310 |
-
onChange={(value,
|
1311 |
/>
|
1312 |
</div>
|
1313 |
|
@@ -1316,7 +1251,7 @@ export default function MapDetailPage() {
|
|
1316 |
name="drone-images"
|
1317 |
label="Drone Images"
|
1318 |
value={droneImagesSelected}
|
1319 |
-
onChange={(value,
|
1320 |
/>
|
1321 |
</div>
|
1322 |
</div>
|
@@ -1330,7 +1265,6 @@ export default function MapDetailPage() {
|
|
1330 |
return;
|
1331 |
}
|
1332 |
|
1333 |
-
// For MapDetailPage, we only export the current image if its type is selected
|
1334 |
if ((map?.image_type === 'crisis_map' && crisisMapsSelected) ||
|
1335 |
(map?.image_type === 'drone_image' && droneImagesSelected)) {
|
1336 |
exportDataset(exportMode);
|
|
|
53 |
const [hasNext, setHasNext] = useState(false);
|
54 |
const [isNavigating, setIsNavigating] = useState(false);
|
55 |
|
|
|
56 |
const [showDeleteConfirm, setShowDeleteConfirm] = useState(false);
|
57 |
const [showExportModal, setShowExportModal] = useState(false);
|
58 |
const [exportMode, setExportMode] = useState<'standard' | 'fine-tuning'>('standard');
|
|
|
62 |
const [crisisMapsSelected, setCrisisMapsSelected] = useState(true);
|
63 |
const [droneImagesSelected, setDroneImagesSelected] = useState(true);
|
64 |
|
|
|
65 |
const [isDeleting, setIsDeleting] = useState(false);
|
66 |
|
|
|
67 |
const {
|
68 |
search, setSearch,
|
69 |
srcFilter, setSrcFilter,
|
|
|
113 |
|
114 |
// Auto-navigate to first matching item when filters change
|
115 |
useEffect(() => {
|
116 |
+
if (!map || loading || isDeleting) return;
|
117 |
|
|
|
118 |
const currentMapMatches = () => {
|
119 |
const matchesSearch = !search ||
|
120 |
map.title?.toLowerCase().includes(search.toLowerCase()) ||
|
|
|
172 |
if (response.ok) {
|
173 |
const images = await response.json();
|
174 |
|
|
|
175 |
const filteredImages = images.filter((img: any) => {
|
176 |
const matchesSearch = !search ||
|
177 |
img.title?.toLowerCase().includes(search.toLowerCase()) ||
|
|
|
204 |
const navigateToItem = async (direction: 'previous' | 'next') => {
|
205 |
if (isNavigating) return;
|
206 |
|
207 |
+
setIsNavigating(true);
|
208 |
try {
|
209 |
const response = await fetch('/api/images');
|
210 |
+
if (response.ok) {
|
211 |
+
const images = await response.json();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
212 |
|
213 |
+
const filteredImages = images.filter((img: any) => {
|
214 |
+
const matchesSearch = !search ||
|
215 |
+
img.title?.toLowerCase().includes(search.toLowerCase()) ||
|
216 |
+
img.generated?.toLowerCase().includes(search.toLowerCase()) ||
|
217 |
+
img.source?.toLowerCase().includes(search.toLowerCase()) ||
|
218 |
+
img.event_type?.toLowerCase().includes(search.toLowerCase());
|
219 |
+
|
220 |
+
const matchesSource = !srcFilter || img.source === srcFilter;
|
221 |
+
const matchesCategory = !catFilter || img.event_type === catFilter;
|
222 |
+
const matchesRegion = !regionFilter ||
|
223 |
+
img.countries?.some((country: any) => country.r_code === regionFilter);
|
224 |
+
const matchesCountry = !countryFilter ||
|
225 |
+
img.countries?.some((country: any) => country.c_code === countryFilter);
|
226 |
+
const matchesImageType = !imageTypeFilter || img.image_type === imageTypeFilter;
|
227 |
+
const matchesReferenceExamples = !showReferenceExamples || img.starred === true;
|
228 |
+
|
229 |
+
return matchesSearch && matchesSource && matchesCategory && matchesRegion && matchesCountry && matchesImageType && matchesReferenceExamples;
|
230 |
+
});
|
231 |
|
232 |
+
const currentIndex = filteredImages.findIndex((img: { image_id: string }) => img.image_id === mapId);
|
233 |
+
|
234 |
+
if (currentIndex === -1) {
|
235 |
+
console.error('Current image not found in filtered list');
|
236 |
+
return;
|
237 |
+
}
|
238 |
+
|
239 |
+
let targetIndex: number;
|
240 |
+
if (direction === 'previous') {
|
241 |
+
targetIndex = currentIndex > 0 ? currentIndex - 1 : filteredImages.length - 1;
|
242 |
+
} else {
|
243 |
+
targetIndex = currentIndex < filteredImages.length - 1 ? currentIndex + 1 : 0;
|
244 |
+
}
|
245 |
+
|
246 |
+
const targetImage = filteredImages[targetIndex];
|
247 |
+
if (targetImage) {
|
248 |
+
navigate(`/map/${targetImage.image_id}`);
|
249 |
+
}
|
250 |
}
|
|
|
|
|
|
|
251 |
} catch (error) {
|
252 |
+
console.error('Failed to navigate to item:', error);
|
253 |
+
} finally {
|
254 |
+
setIsNavigating(false);
|
255 |
}
|
256 |
};
|
257 |
|
|
|
273 |
|
274 |
const [isGenerating, setIsGenerating] = useState(false);
|
275 |
|
276 |
+
// delete function
|
277 |
const handleDelete = async () => {
|
278 |
if (!map) return;
|
279 |
|
|
|
295 |
});
|
296 |
|
297 |
if (response.ok) {
|
|
|
298 |
setMap(prev => prev ? { ...prev, starred: !prev.starred } : null);
|
299 |
} else {
|
300 |
console.error('Failed to toggle starred status');
|
|
|
307 |
const confirmDelete = async () => {
|
308 |
if (!map) return;
|
309 |
|
310 |
+
setIsDeleting(true);
|
311 |
try {
|
312 |
console.log('Deleting image with ID:', map.image_id);
|
313 |
+
const response = await fetch(`/api/images/${map.image_id}`, {
|
314 |
+
method: 'DELETE',
|
315 |
});
|
316 |
|
317 |
+
if (response.ok) {
|
318 |
+
setMap(prev => prev ? { ...prev, starred: !prev.starred } : null);
|
319 |
+
setShowDeleteConfirm(false);
|
320 |
+
|
321 |
+
try {
|
322 |
+
const response = await fetch('/api/images');
|
323 |
+
if (response.ok) {
|
324 |
+
const images = await response.json();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
325 |
|
326 |
+
const filteredImages = images.filter((img: any) => {
|
327 |
+
const matchesSearch = !search ||
|
328 |
+
img.title?.toLowerCase().includes(search.toLowerCase()) ||
|
329 |
+
img.generated?.toLowerCase().includes(search.toLowerCase()) ||
|
330 |
+
img.source?.toLowerCase().includes(search.toLowerCase()) ||
|
331 |
+
img.event_type?.toLowerCase().includes(search.toLowerCase());
|
332 |
+
|
333 |
+
const matchesSource = !srcFilter || img.source === srcFilter;
|
334 |
+
const matchesCategory = !catFilter || img.event_type === catFilter;
|
335 |
+
const matchesRegion = !regionFilter ||
|
336 |
+
img.countries?.some((country: any) => country.r_code === regionFilter);
|
337 |
+
const matchesCountry = !countryFilter ||
|
338 |
+
img.countries?.some((country: any) => country.c_code === countryFilter);
|
339 |
+
const matchesImageType = !imageTypeFilter || img.image_type === imageTypeFilter;
|
340 |
+
const matchesReferenceExamples = !showReferenceExamples || img.starred === true;
|
341 |
+
|
342 |
+
return matchesSearch && matchesSource && matchesCategory && matchesRegion && matchesCountry && matchesImageType && matchesReferenceExamples;
|
343 |
+
});
|
344 |
|
345 |
+
const remainingImages = filteredImages.filter((img: any) => img.image_id !== map.image_id);
|
346 |
|
347 |
+
if (remainingImages.length > 0) {
|
348 |
+
const currentIndex = filteredImages.findIndex((img: any) => img.image_id === map.image_id);
|
349 |
+
|
350 |
+
let targetIndex: number;
|
351 |
+
if (currentIndex === filteredImages.length - 1) {
|
352 |
+
targetIndex = currentIndex - 1;
|
353 |
+
} else {
|
354 |
+
targetIndex = currentIndex;
|
355 |
+
}
|
356 |
+
|
357 |
+
console.log('Navigation target:', { currentIndex, targetIndex, targetId: remainingImages[targetIndex]?.image_id });
|
358 |
+
|
359 |
+
if (targetIndex >= 0 && targetIndex < remainingImages.length) {
|
360 |
+
console.log('Navigating to:', remainingImages[targetIndex].image_id);
|
361 |
+
navigate(`/map/${remainingImages[targetIndex].image_id}`);
|
362 |
+
} else {
|
363 |
+
console.log('Fallback navigation to first item:', remainingImages[0].image_id);
|
364 |
+
navigate(`/map/${remainingImages[0].image_id}`);
|
365 |
+
}
|
366 |
} else {
|
367 |
+
console.log('No remaining items, going to explore page');
|
368 |
+
navigate('/explore');
|
|
|
369 |
}
|
370 |
} else {
|
|
|
|
|
371 |
navigate('/explore');
|
372 |
}
|
373 |
+
} catch (error) {
|
374 |
+
console.error('Failed to navigate to next item:', error);
|
375 |
navigate('/explore');
|
376 |
+
} finally {
|
377 |
+
setIsDeleting(false);
|
378 |
}
|
379 |
+
} else {
|
380 |
+
console.error('Delete failed');
|
381 |
+
setIsDeleting(false);
|
|
|
|
|
|
|
382 |
}
|
383 |
} catch (error) {
|
384 |
console.error('Delete failed:', error);
|
385 |
+
setIsDeleting(false);
|
|
|
386 |
}
|
387 |
};
|
388 |
|
|
|
465 |
}
|
466 |
};
|
467 |
|
|
|
468 |
const createImageData = (map: any, fileName: string) => ({
|
469 |
image: `images/${fileName}`,
|
470 |
caption: map.edited || map.generated || '',
|
|
|
480 |
});
|
481 |
|
482 |
const exportDataset = async (mode: 'standard' | 'fine-tuning') => {
|
483 |
+
if (!map) return;
|
484 |
+
|
|
|
|
|
|
|
485 |
try {
|
|
|
486 |
const JSZip = (await import('jszip')).default;
|
487 |
const zip = new JSZip();
|
488 |
|
|
|
489 |
if (map.image_type === 'crisis_map') {
|
490 |
const crisisFolder = zip.folder('crisis_maps_dataset');
|
491 |
const crisisImagesFolder = crisisFolder?.folder('images');
|
492 |
|
493 |
if (crisisImagesFolder) {
|
|
|
494 |
try {
|
495 |
const response = await fetch(map.image_url);
|
496 |
if (!response.ok) throw new Error(`Failed to fetch image ${map.image_id}`);
|
|
|
502 |
crisisImagesFolder.file(fileName, blob);
|
503 |
|
504 |
if (mode === 'fine-tuning') {
|
|
|
505 |
const trainData: any[] = [];
|
506 |
const testData: any[] = [];
|
507 |
const valData: any[] = [];
|
508 |
|
509 |
if (String(map?.image_type) === 'crisis_map') {
|
|
|
|
|
|
|
|
|
|
|
510 |
const random = Math.random();
|
511 |
if (random < trainSplit / 100) {
|
512 |
trainData.push(createImageData(map, '0001'));
|
|
|
516 |
valData.push(createImageData(map, '0001'));
|
517 |
}
|
518 |
} else if (String(map?.image_type) === 'drone_image') {
|
|
|
|
|
|
|
|
|
|
|
519 |
const random = Math.random();
|
520 |
if (random < trainSplit / 100) {
|
521 |
trainData.push(createImageData(map, '0001'));
|
|
|
526 |
}
|
527 |
}
|
528 |
|
|
|
529 |
if (crisisFolder) {
|
530 |
crisisFolder.file('train.jsonl', JSON.stringify(trainData, null, 2));
|
531 |
crisisFolder.file('test.jsonl', JSON.stringify(testData, null, 2));
|
532 |
crisisFolder.file('val.jsonl', JSON.stringify(valData, null, 2));
|
533 |
}
|
534 |
} else {
|
|
|
535 |
const jsonData = {
|
536 |
image: `images/${fileName}`,
|
537 |
caption: map.edited || map.generated || '',
|
|
|
560 |
const droneImagesFolder = droneFolder?.folder('images');
|
561 |
|
562 |
if (droneImagesFolder) {
|
|
|
563 |
try {
|
564 |
const response = await fetch(map.image_url);
|
565 |
if (!response.ok) throw new Error(`Failed to fetch image ${map.image_id}`);
|
|
|
571 |
droneImagesFolder.file(fileName, blob);
|
572 |
|
573 |
if (mode === 'fine-tuning') {
|
|
|
574 |
const trainData: any[] = [];
|
575 |
const testData: any[] = [];
|
576 |
const valData: any[] = [];
|
577 |
|
578 |
if (String(map?.image_type) === 'crisis_map') {
|
|
|
|
|
|
|
|
|
|
|
579 |
const random = Math.random();
|
580 |
if (random < trainSplit / 100) {
|
581 |
trainData.push(createImageData(map, '0001'));
|
|
|
585 |
valData.push(createImageData(map, '0001'));
|
586 |
}
|
587 |
} else if (String(map?.image_type) === 'drone_image') {
|
|
|
|
|
|
|
|
|
|
|
588 |
const random = Math.random();
|
589 |
if (random < trainSplit / 100) {
|
590 |
trainData.push(createImageData(map, '0001'));
|
|
|
595 |
}
|
596 |
}
|
597 |
|
|
|
598 |
if (droneFolder) {
|
599 |
droneFolder.file('train.jsonl', JSON.stringify(trainData, null, 2));
|
600 |
droneFolder.file('test.jsonl', JSON.stringify(testData, null, 2));
|
601 |
droneFolder.file('val.jsonl', JSON.stringify(valData, null, 2));
|
602 |
}
|
603 |
} else {
|
|
|
604 |
const jsonData = {
|
605 |
image: `images/${fileName}`,
|
606 |
caption: map.edited || map.generated || '',
|
|
|
625 |
}
|
626 |
}
|
627 |
} else {
|
|
|
628 |
const genericFolder = zip.folder('generic_dataset');
|
629 |
const genericImagesFolder = genericFolder?.folder('images');
|
630 |
|
|
|
640 |
genericImagesFolder.file(fileName, blob);
|
641 |
|
642 |
if (mode === 'fine-tuning') {
|
|
|
643 |
const trainData: any[] = [];
|
644 |
const testData: any[] = [];
|
645 |
const valData: any[] = [];
|
646 |
|
647 |
if (String(map?.image_type) === 'crisis_map') {
|
|
|
|
|
|
|
|
|
|
|
648 |
const random = Math.random();
|
649 |
if (random < trainSplit / 100) {
|
650 |
trainData.push(createImageData(map, '0001'));
|
|
|
654 |
valData.push(createImageData(map, '0001'));
|
655 |
}
|
656 |
} else if (String(map?.image_type) === 'drone_image') {
|
|
|
|
|
|
|
|
|
|
|
657 |
const random = Math.random();
|
658 |
if (random < trainSplit / 100) {
|
659 |
trainData.push(createImageData(map, '0001'));
|
|
|
664 |
}
|
665 |
}
|
666 |
|
|
|
667 |
if (genericFolder) {
|
668 |
genericFolder.file('train.jsonl', JSON.stringify(trainData, null, 2));
|
669 |
genericFolder.file('test.jsonl', JSON.stringify(testData, null, 2));
|
670 |
genericFolder.file('val.jsonl', JSON.stringify(valData, null, 2));
|
671 |
}
|
672 |
} else {
|
|
|
673 |
const jsonData = {
|
674 |
image: `images/${fileName}`,
|
675 |
caption: map.edited || map.generated || '',
|
|
|
1177 |
const remaining = 100 - newTrain;
|
1178 |
if (remaining >= 0) {
|
1179 |
setTrainSplit(newTrain);
|
|
|
1180 |
if (testSplit + valSplit > remaining) {
|
1181 |
setTestSplit(Math.floor(remaining / 2));
|
1182 |
setValSplit(remaining - Math.floor(remaining / 2));
|
|
|
1242 |
name="crisis-maps"
|
1243 |
label="Crisis Maps"
|
1244 |
value={crisisMapsSelected}
|
1245 |
+
onChange={(value, _name) => setCrisisMapsSelected(value)}
|
1246 |
/>
|
1247 |
</div>
|
1248 |
|
|
|
1251 |
name="drone-images"
|
1252 |
label="Drone Images"
|
1253 |
value={droneImagesSelected}
|
1254 |
+
onChange={(value, _name) => setDroneImagesSelected(value)}
|
1255 |
/>
|
1256 |
</div>
|
1257 |
</div>
|
|
|
1265 |
return;
|
1266 |
}
|
1267 |
|
|
|
1268 |
if ((map?.image_type === 'crisis_map' && crisisMapsSelected) ||
|
1269 |
(map?.image_type === 'drone_image' && droneImagesSelected)) {
|
1270 |
exportDataset(exportMode);
|
frontend/src/pages/UploadPage/UploadPage.tsx
CHANGED
@@ -32,7 +32,7 @@ export default function UploadPage() {
|
|
32 |
const [countries, setCountries] = useState<string[]>([]);
|
33 |
const [title, setTitle] = useState('');
|
34 |
|
35 |
-
// Drone
|
36 |
const [centerLon, setCenterLon] = useState<string>('');
|
37 |
const [centerLat, setCenterLat] = useState<string>('');
|
38 |
const [amslM, setAmslM] = useState<string>('');
|
@@ -98,7 +98,6 @@ export default function UploadPage() {
|
|
98 |
if (sourcesData.length > 0) setSource(sourcesData[0].s_code);
|
99 |
setEventType('OTHER');
|
100 |
setEpsg('OTHER');
|
101 |
-
// Only set default imageType if we don't have one from URL parameter and don't already have a default
|
102 |
if (imageTypesData.length > 0 && !searchParams.get('imageType') && !imageType) {
|
103 |
setImageType(imageTypesData[0].image_type);
|
104 |
}
|
@@ -106,7 +105,6 @@ export default function UploadPage() {
|
|
106 |
}, [searchParams]);
|
107 |
|
108 |
const handleNavigation = useCallback((to: string) => {
|
109 |
-
// Prevent navigation to upload/home when already on upload page
|
110 |
if (to === '/upload' || to === '/') {
|
111 |
return;
|
112 |
}
|
@@ -141,7 +139,6 @@ export default function UploadPage() {
|
|
141 |
|
142 |
const handleCleanup = () => {
|
143 |
if (uploadedImageIdRef.current) {
|
144 |
-
// This is a user dissatisfaction delete (abandoning upload), so don't pass content_management=true
|
145 |
fetch(`/api/images/${uploadedImageIdRef.current}`, { method: "DELETE" }).catch(console.error);
|
146 |
}
|
147 |
};
|
@@ -186,7 +183,6 @@ export default function UploadPage() {
|
|
186 |
setIsLoadingContribution(true);
|
187 |
setUploadedImageId(imageIdParam);
|
188 |
|
189 |
-
// Set imageType from URL parameter if available
|
190 |
if (imageTypeParam) {
|
191 |
console.log('Setting imageType from URL parameter:', imageTypeParam);
|
192 |
setImageType(imageTypeParam);
|
@@ -196,14 +192,12 @@ export default function UploadPage() {
|
|
196 |
.then(res => res.json())
|
197 |
.then(data => {
|
198 |
console.log('API response data.image_type:', data.image_type);
|
199 |
-
// Only set imageType from API if we don't have it from URL parameter
|
200 |
if (data.image_type && !imageTypeParam) {
|
201 |
console.log('Setting imageType from API response:', data.image_type);
|
202 |
setImageType(data.image_type);
|
203 |
}
|
204 |
|
205 |
if (data.generated) {
|
206 |
-
// Handle both plain text and JSON responses
|
207 |
try {
|
208 |
const parsedGenerated = JSON.parse(data.generated);
|
209 |
if (parsedGenerated.analysis) {
|
@@ -212,7 +206,6 @@ export default function UploadPage() {
|
|
212 |
setDraft(data.generated);
|
213 |
}
|
214 |
} catch (e) {
|
215 |
-
// Not JSON, treat as plain text
|
216 |
setDraft(data.generated);
|
217 |
}
|
218 |
}
|
@@ -269,7 +262,6 @@ export default function UploadPage() {
|
|
269 |
}
|
270 |
}, [searchParams]);
|
271 |
|
272 |
-
// Debug useEffect to track imageType changes
|
273 |
useEffect(() => {
|
274 |
console.log('imageType changed to:', imageType);
|
275 |
}, [imageType]);
|
@@ -337,7 +329,6 @@ export default function UploadPage() {
|
|
337 |
error?: string;
|
338 |
} | null>(null);
|
339 |
|
340 |
-
// Enhanced preprocessing flow state
|
341 |
const [showPreprocessingModal, setShowPreprocessingModal] = useState(false);
|
342 |
const [preprocessingFile, setPreprocessingFile] = useState<File | null>(null);
|
343 |
const [isPreprocessing, setIsPreprocessing] = useState(false);
|
@@ -348,7 +339,6 @@ export default function UploadPage() {
|
|
348 |
e.preventDefault();
|
349 |
const dropped = e.dataTransfer.files?.[0];
|
350 |
if (dropped) {
|
351 |
-
// Use onFileChange to trigger preprocessing detection
|
352 |
onFileChange(dropped);
|
353 |
}
|
354 |
};
|
@@ -369,33 +359,20 @@ export default function UploadPage() {
|
|
369 |
}
|
370 |
};
|
371 |
|
372 |
-
// Check if file needs preprocessing (non-JPEG/PNG)
|
373 |
const needsPreprocessing = (file: File): boolean => {
|
374 |
const supportedTypes = ['image/jpeg', 'image/jpg', 'image/png'];
|
375 |
const supportedExtensions = ['.jpg', '.jpeg', '.png'];
|
376 |
|
377 |
-
// Check MIME type first
|
378 |
let needsPreprocess = !supportedTypes.includes(file.type);
|
379 |
|
380 |
-
// If MIME type check is inconclusive, check file extension
|
381 |
if (!needsPreprocess && file.name) {
|
382 |
const fileExtension = file.name.toLowerCase().substring(file.name.lastIndexOf('.'));
|
383 |
needsPreprocess = !supportedExtensions.includes(fileExtension);
|
384 |
}
|
385 |
|
386 |
-
console.log('Preprocessing check:', {
|
387 |
-
fileName: file.name,
|
388 |
-
fileType: file.type,
|
389 |
-
fileExtension: file.name ? file.name.toLowerCase().substring(file.name.lastIndexOf('.')) : 'none',
|
390 |
-
supportedTypes,
|
391 |
-
supportedExtensions,
|
392 |
-
needsPreprocess
|
393 |
-
});
|
394 |
-
|
395 |
return needsPreprocess;
|
396 |
};
|
397 |
|
398 |
-
// Handle preprocessing confirmation
|
399 |
const handlePreprocessingConfirm = async () => {
|
400 |
if (!preprocessingFile) return;
|
401 |
|
@@ -403,14 +380,12 @@ export default function UploadPage() {
|
|
403 |
setPreprocessingProgress('Starting file conversion...');
|
404 |
|
405 |
try {
|
406 |
-
// Create FormData for preprocessing
|
407 |
const formData = new FormData();
|
408 |
formData.append('file', preprocessingFile);
|
409 |
-
formData.append('preprocess_only', 'true');
|
410 |
|
411 |
setPreprocessingProgress('Converting file format...');
|
412 |
|
413 |
-
// Call preprocessing endpoint
|
414 |
const response = await fetch('/api/images/preprocess', {
|
415 |
method: 'POST',
|
416 |
body: formData
|
@@ -424,30 +399,25 @@ export default function UploadPage() {
|
|
424 |
|
425 |
setPreprocessingProgress('Finalizing conversion...');
|
426 |
|
427 |
-
// Decode base64 content
|
428 |
const processedContent = atob(result.processed_content);
|
429 |
const processedBytes = new Uint8Array(processedContent.length);
|
430 |
for (let i = 0; i < processedContent.length; i++) {
|
431 |
processedBytes[i] = processedContent.charCodeAt(i);
|
432 |
}
|
433 |
|
434 |
-
// Create a new File object from the processed data
|
435 |
const processedFile = new File(
|
436 |
[processedBytes],
|
437 |
result.processed_filename,
|
438 |
{ type: result.processed_mime_type }
|
439 |
);
|
440 |
|
441 |
-
// Create preview URL
|
442 |
const previewUrl = URL.createObjectURL(processedFile);
|
443 |
|
444 |
-
// Update the main file state
|
445 |
setFile(processedFile);
|
446 |
setPreview(previewUrl);
|
447 |
|
448 |
setPreprocessingProgress('Conversion complete!');
|
449 |
|
450 |
-
// Close modal after a brief delay
|
451 |
setTimeout(() => {
|
452 |
setShowPreprocessingModal(false);
|
453 |
setPreprocessingFile(null);
|
@@ -467,7 +437,6 @@ export default function UploadPage() {
|
|
467 |
}
|
468 |
};
|
469 |
|
470 |
-
// Handle preprocessing cancellation
|
471 |
const handlePreprocessingCancel = () => {
|
472 |
setShowPreprocessingModal(false);
|
473 |
setPreprocessingFile(null);
|
@@ -511,7 +480,6 @@ export default function UploadPage() {
|
|
511 |
if (imageType === 'drone_image') {
|
512 |
fd.append('event_type', eventType || 'OTHER');
|
513 |
fd.append('epsg', epsg || 'OTHER');
|
514 |
-
// Add drone-specific metadata
|
515 |
if (centerLon) fd.append('center_lon', centerLon);
|
516 |
if (centerLat) fd.append('center_lat', centerLat);
|
517 |
if (amslM) fd.append('amsl_m', amslM);
|
@@ -543,7 +511,6 @@ export default function UploadPage() {
|
|
543 |
if (!mapRes.ok) throw new Error((mapJson.error as string) || 'Upload failed');
|
544 |
setImageUrl(mapJson.image_url as string);
|
545 |
|
546 |
-
// Check for preprocessing info and show notification if needed
|
547 |
if (mapJson.preprocessing_info &&
|
548 |
typeof mapJson.preprocessing_info === 'object' &&
|
549 |
'was_preprocessed' in mapJson.preprocessing_info &&
|
@@ -574,7 +541,6 @@ export default function UploadPage() {
|
|
574 |
if (!capRes.ok) throw new Error((capJson.error as string) || 'Caption failed');
|
575 |
setUploadedImageId(mapIdVal);
|
576 |
|
577 |
-
// Check for model fallback information
|
578 |
const fallbackInfo = (capJson.raw_json as Record<string, unknown>)?.fallback_info;
|
579 |
if (fallbackInfo) {
|
580 |
setFallbackInfo({
|
@@ -611,7 +577,6 @@ export default function UploadPage() {
|
|
611 |
}
|
612 |
}
|
613 |
|
614 |
-
// Handle both plain text and JSON responses
|
615 |
if (capJson.generated) {
|
616 |
try {
|
617 |
const parsedGenerated = JSON.parse(capJson.generated as string);
|
@@ -621,7 +586,6 @@ export default function UploadPage() {
|
|
621 |
setDraft(capJson.generated as string);
|
622 |
}
|
623 |
} catch (e) {
|
624 |
-
// Not JSON, treat as plain text
|
625 |
setDraft(capJson.generated as string);
|
626 |
}
|
627 |
}
|
@@ -665,7 +629,6 @@ export default function UploadPage() {
|
|
665 |
const json = await readJsonSafely(res);
|
666 |
if (!res.ok) throw new Error((json.error as string) || 'Upload failed');
|
667 |
|
668 |
-
// Check for preprocessing info and show notification if needed
|
669 |
if (json.preprocessing_info &&
|
670 |
typeof json.preprocessing_info === 'object' &&
|
671 |
'was_preprocessed' in json.preprocessing_info &&
|
@@ -691,7 +654,6 @@ export default function UploadPage() {
|
|
691 |
const capJson = await readJsonSafely(capRes);
|
692 |
if (!capRes.ok) throw new Error((capJson.error as string) || 'Caption failed');
|
693 |
|
694 |
-
// Check for model fallback information
|
695 |
const fallbackInfo = (capJson.raw_json as Record<string, unknown>)?.fallback_info;
|
696 |
if (fallbackInfo) {
|
697 |
setFallbackInfo({
|
@@ -712,7 +674,6 @@ export default function UploadPage() {
|
|
712 |
if ((metadata as Record<string, unknown>).countries && Array.isArray((metadata as Record<string, unknown>).countries)) {
|
713 |
setCountries((metadata as Record<string, unknown>).countries as string[]);
|
714 |
}
|
715 |
-
// Extract drone metadata if available
|
716 |
if (imageType === 'drone_image') {
|
717 |
if ((metadata as Record<string, unknown>).center_lon) setCenterLon((metadata as Record<string, unknown>).center_lon as string);
|
718 |
if ((metadata as Record<string, unknown>).center_lat) setCenterLat((metadata as Record<string, unknown>).center_lat as string);
|
@@ -728,7 +689,6 @@ export default function UploadPage() {
|
|
728 |
}
|
729 |
}
|
730 |
|
731 |
-
// Handle both plain text and JSON responses
|
732 |
if (capJson.generated) {
|
733 |
try {
|
734 |
const parsedGenerated = JSON.parse(capJson.generated as string);
|
@@ -738,7 +698,6 @@ export default function UploadPage() {
|
|
738 |
setDraft(capJson.generated as string);
|
739 |
}
|
740 |
} catch (e) {
|
741 |
-
// Not JSON, treat as plain text
|
742 |
setDraft(capJson.generated as string);
|
743 |
}
|
744 |
}
|
@@ -921,7 +880,6 @@ export default function UploadPage() {
|
|
921 |
</>
|
922 |
)}
|
923 |
|
924 |
-
{/* File-picker button - always visible */}
|
925 |
<label className="inline-block cursor-pointer">
|
926 |
<input
|
927 |
type="file"
|
@@ -941,7 +899,6 @@ export default function UploadPage() {
|
|
941 |
</div>
|
942 |
)}
|
943 |
|
944 |
-
{/* Loading state */}
|
945 |
{isLoading && (
|
946 |
<div className={styles.loadingContainer}>
|
947 |
<Spinner className="text-ifrcRed" />
|
@@ -949,7 +906,6 @@ export default function UploadPage() {
|
|
949 |
</div>
|
950 |
)}
|
951 |
|
952 |
-
{/* Loading contribution data */}
|
953 |
{isLoadingContribution && (
|
954 |
<div className={styles.loadingContainer}>
|
955 |
<Spinner className="text-ifrcRed" />
|
@@ -957,7 +913,6 @@ export default function UploadPage() {
|
|
957 |
</div>
|
958 |
)}
|
959 |
|
960 |
-
{/* Generate button */}
|
961 |
{step === 1 && !isLoading && (
|
962 |
<div className={styles.generateButtonContainer}>
|
963 |
{imageUrl ? (
|
@@ -981,7 +936,6 @@ export default function UploadPage() {
|
|
981 |
|
982 |
{step === '2a' && (
|
983 |
<div className={styles.step2Layout}>
|
984 |
-
{/* Left Column - Map */}
|
985 |
<div className={styles.mapColumn}>
|
986 |
<Container heading="Uploaded Image" headingLevel={3} withHeaderBorder withInternalPadding>
|
987 |
<div className={styles.uploadedMapContainer}>
|
@@ -1005,7 +959,6 @@ export default function UploadPage() {
|
|
1005 |
</Container>
|
1006 |
</div>
|
1007 |
|
1008 |
-
{/* Right Column - Metadata Form */}
|
1009 |
<div className={styles.contentColumn}>
|
1010 |
<div className={styles.metadataSectionCard}>
|
1011 |
<Container
|
@@ -1079,7 +1032,6 @@ export default function UploadPage() {
|
|
1079 |
placeholder="Select one or more"
|
1080 |
/>
|
1081 |
|
1082 |
-
{/* Drone-specific metadata fields */}
|
1083 |
{imageType === 'drone_image' && (
|
1084 |
<>
|
1085 |
<div className={styles.droneMetadataSection}>
|
@@ -1214,7 +1166,6 @@ export default function UploadPage() {
|
|
1214 |
|
1215 |
{step === '2b' && (
|
1216 |
<div className={styles.step2Layout}>
|
1217 |
-
{/* Left Column - Map */}
|
1218 |
<div className={styles.mapColumn}>
|
1219 |
<Container heading="Uploaded Image" headingLevel={3} withHeaderBorder withInternalPadding>
|
1220 |
<div className={styles.uploadedMapContainer}>
|
@@ -1294,7 +1245,6 @@ export default function UploadPage() {
|
|
1294 |
</Container>
|
1295 |
</div>
|
1296 |
|
1297 |
-
{/* ────── AI‑GENERATED CAPTION ────── */}
|
1298 |
<div className={styles.metadataSectionCard}>
|
1299 |
<Container
|
1300 |
heading="Generated Text"
|
@@ -1542,7 +1492,7 @@ export default function UploadPage() {
|
|
1542 |
</div>
|
1543 |
)}
|
1544 |
|
1545 |
-
{/* Success page
|
1546 |
{step === 3 && (
|
1547 |
<div className={styles.successContainer}>
|
1548 |
<Heading level={2} className={styles.successHeading}>Saved!</Heading>
|
|
|
32 |
const [countries, setCountries] = useState<string[]>([]);
|
33 |
const [title, setTitle] = useState('');
|
34 |
|
35 |
+
// Drone metadata fields
|
36 |
const [centerLon, setCenterLon] = useState<string>('');
|
37 |
const [centerLat, setCenterLat] = useState<string>('');
|
38 |
const [amslM, setAmslM] = useState<string>('');
|
|
|
98 |
if (sourcesData.length > 0) setSource(sourcesData[0].s_code);
|
99 |
setEventType('OTHER');
|
100 |
setEpsg('OTHER');
|
|
|
101 |
if (imageTypesData.length > 0 && !searchParams.get('imageType') && !imageType) {
|
102 |
setImageType(imageTypesData[0].image_type);
|
103 |
}
|
|
|
105 |
}, [searchParams]);
|
106 |
|
107 |
const handleNavigation = useCallback((to: string) => {
|
|
|
108 |
if (to === '/upload' || to === '/') {
|
109 |
return;
|
110 |
}
|
|
|
139 |
|
140 |
const handleCleanup = () => {
|
141 |
if (uploadedImageIdRef.current) {
|
|
|
142 |
fetch(`/api/images/${uploadedImageIdRef.current}`, { method: "DELETE" }).catch(console.error);
|
143 |
}
|
144 |
};
|
|
|
183 |
setIsLoadingContribution(true);
|
184 |
setUploadedImageId(imageIdParam);
|
185 |
|
|
|
186 |
if (imageTypeParam) {
|
187 |
console.log('Setting imageType from URL parameter:', imageTypeParam);
|
188 |
setImageType(imageTypeParam);
|
|
|
192 |
.then(res => res.json())
|
193 |
.then(data => {
|
194 |
console.log('API response data.image_type:', data.image_type);
|
|
|
195 |
if (data.image_type && !imageTypeParam) {
|
196 |
console.log('Setting imageType from API response:', data.image_type);
|
197 |
setImageType(data.image_type);
|
198 |
}
|
199 |
|
200 |
if (data.generated) {
|
|
|
201 |
try {
|
202 |
const parsedGenerated = JSON.parse(data.generated);
|
203 |
if (parsedGenerated.analysis) {
|
|
|
206 |
setDraft(data.generated);
|
207 |
}
|
208 |
} catch (e) {
|
|
|
209 |
setDraft(data.generated);
|
210 |
}
|
211 |
}
|
|
|
262 |
}
|
263 |
}, [searchParams]);
|
264 |
|
|
|
265 |
useEffect(() => {
|
266 |
console.log('imageType changed to:', imageType);
|
267 |
}, [imageType]);
|
|
|
329 |
error?: string;
|
330 |
} | null>(null);
|
331 |
|
|
|
332 |
const [showPreprocessingModal, setShowPreprocessingModal] = useState(false);
|
333 |
const [preprocessingFile, setPreprocessingFile] = useState<File | null>(null);
|
334 |
const [isPreprocessing, setIsPreprocessing] = useState(false);
|
|
|
339 |
e.preventDefault();
|
340 |
const dropped = e.dataTransfer.files?.[0];
|
341 |
if (dropped) {
|
|
|
342 |
onFileChange(dropped);
|
343 |
}
|
344 |
};
|
|
|
359 |
}
|
360 |
};
|
361 |
|
|
|
362 |
const needsPreprocessing = (file: File): boolean => {
|
363 |
const supportedTypes = ['image/jpeg', 'image/jpg', 'image/png'];
|
364 |
const supportedExtensions = ['.jpg', '.jpeg', '.png'];
|
365 |
|
|
|
366 |
let needsPreprocess = !supportedTypes.includes(file.type);
|
367 |
|
|
|
368 |
if (!needsPreprocess && file.name) {
|
369 |
const fileExtension = file.name.toLowerCase().substring(file.name.lastIndexOf('.'));
|
370 |
needsPreprocess = !supportedExtensions.includes(fileExtension);
|
371 |
}
|
372 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
373 |
return needsPreprocess;
|
374 |
};
|
375 |
|
|
|
376 |
const handlePreprocessingConfirm = async () => {
|
377 |
if (!preprocessingFile) return;
|
378 |
|
|
|
380 |
setPreprocessingProgress('Starting file conversion...');
|
381 |
|
382 |
try {
|
|
|
383 |
const formData = new FormData();
|
384 |
formData.append('file', preprocessingFile);
|
385 |
+
formData.append('preprocess_only', 'true');
|
386 |
|
387 |
setPreprocessingProgress('Converting file format...');
|
388 |
|
|
|
389 |
const response = await fetch('/api/images/preprocess', {
|
390 |
method: 'POST',
|
391 |
body: formData
|
|
|
399 |
|
400 |
setPreprocessingProgress('Finalizing conversion...');
|
401 |
|
|
|
402 |
const processedContent = atob(result.processed_content);
|
403 |
const processedBytes = new Uint8Array(processedContent.length);
|
404 |
for (let i = 0; i < processedContent.length; i++) {
|
405 |
processedBytes[i] = processedContent.charCodeAt(i);
|
406 |
}
|
407 |
|
|
|
408 |
const processedFile = new File(
|
409 |
[processedBytes],
|
410 |
result.processed_filename,
|
411 |
{ type: result.processed_mime_type }
|
412 |
);
|
413 |
|
|
|
414 |
const previewUrl = URL.createObjectURL(processedFile);
|
415 |
|
|
|
416 |
setFile(processedFile);
|
417 |
setPreview(previewUrl);
|
418 |
|
419 |
setPreprocessingProgress('Conversion complete!');
|
420 |
|
|
|
421 |
setTimeout(() => {
|
422 |
setShowPreprocessingModal(false);
|
423 |
setPreprocessingFile(null);
|
|
|
437 |
}
|
438 |
};
|
439 |
|
|
|
440 |
const handlePreprocessingCancel = () => {
|
441 |
setShowPreprocessingModal(false);
|
442 |
setPreprocessingFile(null);
|
|
|
480 |
if (imageType === 'drone_image') {
|
481 |
fd.append('event_type', eventType || 'OTHER');
|
482 |
fd.append('epsg', epsg || 'OTHER');
|
|
|
483 |
if (centerLon) fd.append('center_lon', centerLon);
|
484 |
if (centerLat) fd.append('center_lat', centerLat);
|
485 |
if (amslM) fd.append('amsl_m', amslM);
|
|
|
511 |
if (!mapRes.ok) throw new Error((mapJson.error as string) || 'Upload failed');
|
512 |
setImageUrl(mapJson.image_url as string);
|
513 |
|
|
|
514 |
if (mapJson.preprocessing_info &&
|
515 |
typeof mapJson.preprocessing_info === 'object' &&
|
516 |
'was_preprocessed' in mapJson.preprocessing_info &&
|
|
|
541 |
if (!capRes.ok) throw new Error((capJson.error as string) || 'Caption failed');
|
542 |
setUploadedImageId(mapIdVal);
|
543 |
|
|
|
544 |
const fallbackInfo = (capJson.raw_json as Record<string, unknown>)?.fallback_info;
|
545 |
if (fallbackInfo) {
|
546 |
setFallbackInfo({
|
|
|
577 |
}
|
578 |
}
|
579 |
|
|
|
580 |
if (capJson.generated) {
|
581 |
try {
|
582 |
const parsedGenerated = JSON.parse(capJson.generated as string);
|
|
|
586 |
setDraft(capJson.generated as string);
|
587 |
}
|
588 |
} catch (e) {
|
|
|
589 |
setDraft(capJson.generated as string);
|
590 |
}
|
591 |
}
|
|
|
629 |
const json = await readJsonSafely(res);
|
630 |
if (!res.ok) throw new Error((json.error as string) || 'Upload failed');
|
631 |
|
|
|
632 |
if (json.preprocessing_info &&
|
633 |
typeof json.preprocessing_info === 'object' &&
|
634 |
'was_preprocessed' in json.preprocessing_info &&
|
|
|
654 |
const capJson = await readJsonSafely(capRes);
|
655 |
if (!capRes.ok) throw new Error((capJson.error as string) || 'Caption failed');
|
656 |
|
|
|
657 |
const fallbackInfo = (capJson.raw_json as Record<string, unknown>)?.fallback_info;
|
658 |
if (fallbackInfo) {
|
659 |
setFallbackInfo({
|
|
|
674 |
if ((metadata as Record<string, unknown>).countries && Array.isArray((metadata as Record<string, unknown>).countries)) {
|
675 |
setCountries((metadata as Record<string, unknown>).countries as string[]);
|
676 |
}
|
|
|
677 |
if (imageType === 'drone_image') {
|
678 |
if ((metadata as Record<string, unknown>).center_lon) setCenterLon((metadata as Record<string, unknown>).center_lon as string);
|
679 |
if ((metadata as Record<string, unknown>).center_lat) setCenterLat((metadata as Record<string, unknown>).center_lat as string);
|
|
|
689 |
}
|
690 |
}
|
691 |
|
|
|
692 |
if (capJson.generated) {
|
693 |
try {
|
694 |
const parsedGenerated = JSON.parse(capJson.generated as string);
|
|
|
698 |
setDraft(capJson.generated as string);
|
699 |
}
|
700 |
} catch (e) {
|
|
|
701 |
setDraft(capJson.generated as string);
|
702 |
}
|
703 |
}
|
|
|
880 |
</>
|
881 |
)}
|
882 |
|
|
|
883 |
<label className="inline-block cursor-pointer">
|
884 |
<input
|
885 |
type="file"
|
|
|
899 |
</div>
|
900 |
)}
|
901 |
|
|
|
902 |
{isLoading && (
|
903 |
<div className={styles.loadingContainer}>
|
904 |
<Spinner className="text-ifrcRed" />
|
|
|
906 |
</div>
|
907 |
)}
|
908 |
|
|
|
909 |
{isLoadingContribution && (
|
910 |
<div className={styles.loadingContainer}>
|
911 |
<Spinner className="text-ifrcRed" />
|
|
|
913 |
</div>
|
914 |
)}
|
915 |
|
|
|
916 |
{step === 1 && !isLoading && (
|
917 |
<div className={styles.generateButtonContainer}>
|
918 |
{imageUrl ? (
|
|
|
936 |
|
937 |
{step === '2a' && (
|
938 |
<div className={styles.step2Layout}>
|
|
|
939 |
<div className={styles.mapColumn}>
|
940 |
<Container heading="Uploaded Image" headingLevel={3} withHeaderBorder withInternalPadding>
|
941 |
<div className={styles.uploadedMapContainer}>
|
|
|
959 |
</Container>
|
960 |
</div>
|
961 |
|
|
|
962 |
<div className={styles.contentColumn}>
|
963 |
<div className={styles.metadataSectionCard}>
|
964 |
<Container
|
|
|
1032 |
placeholder="Select one or more"
|
1033 |
/>
|
1034 |
|
|
|
1035 |
{imageType === 'drone_image' && (
|
1036 |
<>
|
1037 |
<div className={styles.droneMetadataSection}>
|
|
|
1166 |
|
1167 |
{step === '2b' && (
|
1168 |
<div className={styles.step2Layout}>
|
|
|
1169 |
<div className={styles.mapColumn}>
|
1170 |
<Container heading="Uploaded Image" headingLevel={3} withHeaderBorder withInternalPadding>
|
1171 |
<div className={styles.uploadedMapContainer}>
|
|
|
1245 |
</Container>
|
1246 |
</div>
|
1247 |
|
|
|
1248 |
<div className={styles.metadataSectionCard}>
|
1249 |
<Container
|
1250 |
heading="Generated Text"
|
|
|
1492 |
</div>
|
1493 |
)}
|
1494 |
|
1495 |
+
{/* Success page */}
|
1496 |
{step === 3 && (
|
1497 |
<div className={styles.successContainer}>
|
1498 |
<Heading level={2} className={styles.successHeading}>Saved!</Heading>
|
go-web-app-develop/.changeset/README.md
DELETED
@@ -1,8 +0,0 @@
|
|
1 |
-
# Changesets
|
2 |
-
|
3 |
-
Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works
|
4 |
-
with multi-package repos, or single-package repos to help you version and publish your code. You can
|
5 |
-
find the full documentation for it [in our repository](https://github.com/changesets/changesets)
|
6 |
-
|
7 |
-
We have a quick list of common questions to get you started engaging with this project in
|
8 |
-
[our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
go-web-app-develop/.changeset/config.json
DELETED
@@ -1,15 +0,0 @@
|
|
1 |
-
{
|
2 |
-
"$schema": "https://unpkg.com/@changesets/[email protected]/schema.json",
|
3 |
-
"changelog": "@changesets/cli/changelog",
|
4 |
-
"commit": false,
|
5 |
-
"fixed": [],
|
6 |
-
"linked": [],
|
7 |
-
"access": "public",
|
8 |
-
"baseBranch": "develop",
|
9 |
-
"updateInternalDependencies": "patch",
|
10 |
-
"ignore": [],
|
11 |
-
"privatePackages": {
|
12 |
-
"version": true,
|
13 |
-
"tag": true
|
14 |
-
}
|
15 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
go-web-app-develop/.changeset/lovely-kids-boil.md
DELETED
@@ -1,5 +0,0 @@
|
|
1 |
-
---
|
2 |
-
"go-web-app": patch
|
3 |
-
---
|
4 |
-
|
5 |
-
Fix use of operational timeframe date in imminent final report form
|
|
|
|
|
|
|
|
|
|
|
|
go-web-app-develop/.changeset/pre.json
DELETED
@@ -1,15 +0,0 @@
|
|
1 |
-
{
|
2 |
-
"mode": "pre",
|
3 |
-
"tag": "beta",
|
4 |
-
"initialVersions": {
|
5 |
-
"go-web-app": "7.20.2",
|
6 |
-
"go-ui-storybook": "1.0.7",
|
7 |
-
"@ifrc-go/ui": "1.5.1"
|
8 |
-
},
|
9 |
-
"changesets": [
|
10 |
-
"lovely-kids-boil",
|
11 |
-
"solid-clubs-care",
|
12 |
-
"sweet-gifts-cheer",
|
13 |
-
"whole-lions-guess"
|
14 |
-
]
|
15 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
go-web-app-develop/.changeset/solid-clubs-care.md
DELETED
@@ -1,8 +0,0 @@
|
|
1 |
-
---
|
2 |
-
"go-web-app": minor
|
3 |
-
---
|
4 |
-
|
5 |
-
Add Crisis categorization update date
|
6 |
-
|
7 |
-
- Add updated date for crisis categorization in emergency page.
|
8 |
-
- Add consent checkbox over situational overview in field report form.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
go-web-app-develop/.changeset/sweet-gifts-cheer.md
DELETED
@@ -1,9 +0,0 @@
|
|
1 |
-
---
|
2 |
-
"go-web-app": minor
|
3 |
-
---
|
4 |
-
|
5 |
-
Add support for DREF imminent v2 in final report
|
6 |
-
|
7 |
-
- Add a separate route for the old dref final report form
|
8 |
-
- Update dref final report to accomodate imminent v2 changes
|
9 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
go-web-app-develop/.changeset/whole-lions-guess.md
DELETED
@@ -1,7 +0,0 @@
|
|
1 |
-
---
|
2 |
-
"go-web-app": patch
|
3 |
-
---
|
4 |
-
|
5 |
-
- Fix calculation of Operation End date in Final report form
|
6 |
-
- Fix icon position issue in the implementation table of DREF PDF export
|
7 |
-
- Update the label for last update date in the crisis categorization pop-up
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
go-web-app-develop/.dockerignore
DELETED
@@ -1,148 +0,0 @@
|
|
1 |
-
# Swap files
|
2 |
-
*.swp
|
3 |
-
|
4 |
-
# Byte-compiled / optimized / DLL files
|
5 |
-
__pycache__
|
6 |
-
*.py[cod]
|
7 |
-
*$py.class
|
8 |
-
|
9 |
-
# C extensions
|
10 |
-
*.so
|
11 |
-
|
12 |
-
# Distribution / packaging
|
13 |
-
.Python
|
14 |
-
env
|
15 |
-
build
|
16 |
-
develop-eggs
|
17 |
-
dist
|
18 |
-
downloads
|
19 |
-
eggs
|
20 |
-
.eggs
|
21 |
-
lib
|
22 |
-
lib64
|
23 |
-
parts
|
24 |
-
sdist
|
25 |
-
var
|
26 |
-
*.egg-info
|
27 |
-
.installed.cfg
|
28 |
-
*.egg
|
29 |
-
|
30 |
-
# PyInstaller
|
31 |
-
# Usually these files are written by a python script from a template
|
32 |
-
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
33 |
-
*.manifest
|
34 |
-
*.spec
|
35 |
-
|
36 |
-
# Installer logs
|
37 |
-
pip-log.txt
|
38 |
-
pip-delete-this-directory.txt
|
39 |
-
|
40 |
-
# Unit test / coverage reports
|
41 |
-
htmlcov
|
42 |
-
.tox
|
43 |
-
.coverage
|
44 |
-
.coverage.*
|
45 |
-
.cache
|
46 |
-
nosetests.xml
|
47 |
-
coverage.xml
|
48 |
-
*,cover
|
49 |
-
.hypothesis
|
50 |
-
|
51 |
-
# Translations
|
52 |
-
*.mo
|
53 |
-
*.pot
|
54 |
-
|
55 |
-
# Django stuff:
|
56 |
-
*.log
|
57 |
-
|
58 |
-
# Sphinx documentation
|
59 |
-
docs/_build
|
60 |
-
|
61 |
-
# PyBuilder
|
62 |
-
target
|
63 |
-
|
64 |
-
#Ipython Notebook
|
65 |
-
.ipynb_checkpoints
|
66 |
-
|
67 |
-
# SASS cache
|
68 |
-
.sass-cache
|
69 |
-
media_test
|
70 |
-
|
71 |
-
# Rope project settings
|
72 |
-
.ropeproject
|
73 |
-
|
74 |
-
# Logs
|
75 |
-
logs
|
76 |
-
*.log
|
77 |
-
npm-debug.log*
|
78 |
-
yarn-debug.log*
|
79 |
-
yarn-error.log*
|
80 |
-
|
81 |
-
# Runtime data
|
82 |
-
pids
|
83 |
-
*.pid
|
84 |
-
*.seed
|
85 |
-
*.pid.lock
|
86 |
-
|
87 |
-
# Directory for instrumented libs generated by jscoverage/JSCover
|
88 |
-
lib-cov
|
89 |
-
|
90 |
-
# Coverage directory used by tools like istanbul
|
91 |
-
coverage
|
92 |
-
|
93 |
-
# nyc test coverage
|
94 |
-
.nyc_output
|
95 |
-
|
96 |
-
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
|
97 |
-
.grunt
|
98 |
-
|
99 |
-
# Bower dependency directory (https://bower.io/)
|
100 |
-
bower_components
|
101 |
-
|
102 |
-
# node-waf configuration
|
103 |
-
.lock-wscript
|
104 |
-
|
105 |
-
# Compiled binary addons (http://nodejs.org/api/addons.html)
|
106 |
-
build/Release
|
107 |
-
|
108 |
-
# Dependency directories
|
109 |
-
node_modules
|
110 |
-
jspm_packages
|
111 |
-
|
112 |
-
# Typescript v1 declaration files
|
113 |
-
typings
|
114 |
-
|
115 |
-
# Optional npm cache directory
|
116 |
-
.npm
|
117 |
-
|
118 |
-
# Optional eslint cache
|
119 |
-
.eslintcache
|
120 |
-
|
121 |
-
# Optional REPL history
|
122 |
-
.node_repl_history
|
123 |
-
|
124 |
-
# Output of 'npm pack'
|
125 |
-
*.tgz
|
126 |
-
|
127 |
-
# Yarn Integrity file
|
128 |
-
.yarn-integrity
|
129 |
-
|
130 |
-
# dotenv environment variables file
|
131 |
-
.env
|
132 |
-
.env*
|
133 |
-
|
134 |
-
# Sensitive Deploy Files
|
135 |
-
deploy/eb/
|
136 |
-
|
137 |
-
# tox
|
138 |
-
./.tox
|
139 |
-
|
140 |
-
# Helm
|
141 |
-
.helm-charts/
|
142 |
-
|
143 |
-
# Docker
|
144 |
-
Dockerfile
|
145 |
-
.dockerignore
|
146 |
-
|
147 |
-
# git
|
148 |
-
.gitignore
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
go-web-app-develop/.github/ISSUE_TEMPLATE/01_bug_report.yml
DELETED
@@ -1,92 +0,0 @@
|
|
1 |
-
name: "Bug Report"
|
2 |
-
description: "Report a technical or visual issue."
|
3 |
-
labels: ["type: bug"]
|
4 |
-
type: "Bug"
|
5 |
-
body:
|
6 |
-
- type: markdown
|
7 |
-
attributes:
|
8 |
-
value: |
|
9 |
-
**Bug Report**
|
10 |
-
Please fill out the form below with as much detail as possible.
|
11 |
-
If the issue is visual, screenshots or videos are greatly appreciated.
|
12 |
-
**Please review [our guide on reporting bugs](https://github.com/IFRCGo/go-web-app/blob/develop/CONTRIBUTING.md#reporting-bugs) before opening a new issue.**
|
13 |
-
|
14 |
-
- type: input
|
15 |
-
attributes:
|
16 |
-
label: "Page URL"
|
17 |
-
description: "The URL of the page where you encountered the issue."
|
18 |
-
placeholder: "https://go.ifrc.org/"
|
19 |
-
validations:
|
20 |
-
required: true
|
21 |
-
|
22 |
-
- type: dropdown
|
23 |
-
attributes:
|
24 |
-
label: "Environment"
|
25 |
-
description: "Please select the environment where the bug occurred."
|
26 |
-
options:
|
27 |
-
- "Alpha"
|
28 |
-
- "Staging"
|
29 |
-
- "Production"
|
30 |
-
validations:
|
31 |
-
required: true
|
32 |
-
|
33 |
-
- type: input
|
34 |
-
attributes:
|
35 |
-
label: "Browser"
|
36 |
-
description: "Which browser are you using? (e.g., Chrome, Firefox, Safari)"
|
37 |
-
placeholder: "Chrome"
|
38 |
-
validations:
|
39 |
-
required: true
|
40 |
-
|
41 |
-
- type: textarea
|
42 |
-
attributes:
|
43 |
-
label: "Steps to Reproduce the Issue"
|
44 |
-
description: |
|
45 |
-
Please describe the issue in detail, including:
|
46 |
-
1. What actions led to the issue?
|
47 |
-
2. If possible, attach screenshots or videos demonstrating the problem.
|
48 |
-
placeholder: |
|
49 |
-
1. I clicked on...
|
50 |
-
2. [Attach screenshots/videos if available]
|
51 |
-
validations:
|
52 |
-
required: true
|
53 |
-
|
54 |
-
- type: textarea
|
55 |
-
attributes:
|
56 |
-
label: "Expected Behavior"
|
57 |
-
description: "Describe what you expected to happen."
|
58 |
-
placeholder: "I expected the page to..."
|
59 |
-
validations:
|
60 |
-
required: true
|
61 |
-
|
62 |
-
- type: textarea
|
63 |
-
attributes:
|
64 |
-
label: "Actual Behavior"
|
65 |
-
description: "Describe what actually happened, including any error messages."
|
66 |
-
placeholder: "Instead, I saw..."
|
67 |
-
validations:
|
68 |
-
required: true
|
69 |
-
|
70 |
-
- type: dropdown
|
71 |
-
attributes:
|
72 |
-
label: "Priority"
|
73 |
-
description: "How urgent is this issue?"
|
74 |
-
options:
|
75 |
-
- "Low (Minor inconvenience)"
|
76 |
-
- "Medium (Affects functionality, but there is a workaround)"
|
77 |
-
- "High (Major functionality is broken)"
|
78 |
-
- "Critical (Site is unusable)"
|
79 |
-
validations:
|
80 |
-
required: false
|
81 |
-
|
82 |
-
- type: textarea
|
83 |
-
attributes:
|
84 |
-
label: "Additional Context (Optional)"
|
85 |
-
description: |
|
86 |
-
Provide any extra details, such as:
|
87 |
-
- Related links.
|
88 |
-
- Previous occurrences of this issue.
|
89 |
-
- Workarounds you have tried.
|
90 |
-
placeholder: "This issue also happened on [link]."
|
91 |
-
validations:
|
92 |
-
required: false
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
go-web-app-develop/.github/ISSUE_TEMPLATE/02_feature_request.yml
DELETED
@@ -1,39 +0,0 @@
|
|
1 |
-
name: "Feature Request"
|
2 |
-
description: "Suggest a new idea or enhancement."
|
3 |
-
labels: ["type: feature-request"]
|
4 |
-
type: "Feature"
|
5 |
-
body:
|
6 |
-
- type: markdown
|
7 |
-
attributes:
|
8 |
-
value: |
|
9 |
-
**Feature Request**
|
10 |
-
Thank you for suggesting a new feature!
|
11 |
-
Please provide as much detail as possible to help us understand and evaluate your idea.
|
12 |
-
**Please review [our guide on suggesting enhancements](https://github.com/IFRCGo/go-web-app/blob/develop/CONTRIBUTING.md#suggesting-enhancements).**
|
13 |
-
|
14 |
-
- type: textarea
|
15 |
-
attributes:
|
16 |
-
label: "Feature Description"
|
17 |
-
description: |
|
18 |
-
Describe your feature request in detail, including:
|
19 |
-
- What the feature is.
|
20 |
-
- Why it is needed and how it will improve the project.
|
21 |
-
- How it will benefit users (e.g., As a user, I want to [do something] so that [desired outcome].).
|
22 |
-
placeholder: "As a user, I want to filter search results by date so that I can quickly find recent information."
|
23 |
-
validations:
|
24 |
-
required: true
|
25 |
-
|
26 |
-
- type: textarea
|
27 |
-
attributes:
|
28 |
-
label: "Additional Context"
|
29 |
-
description: |
|
30 |
-
Provide any extra details or supporting information, such as:
|
31 |
-
- Links to references or related resources.
|
32 |
-
- Examples from other projects or systems.
|
33 |
-
- Screenshots, mockups, or diagrams.
|
34 |
-
*Tip: You can attach files by clicking here and dragging them in.*
|
35 |
-
placeholder: |
|
36 |
-
Here's a link to a similar feature in another project: [link].
|
37 |
-
I've also attached a mockup of what this could look like.
|
38 |
-
validations:
|
39 |
-
required: false
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
go-web-app-develop/.github/ISSUE_TEMPLATE/03_epic_request.yml
DELETED
@@ -1,37 +0,0 @@
|
|
1 |
-
name: "Epic"
|
2 |
-
description: "Track a larger initiative with multiple related tasks and deliverables."
|
3 |
-
labels: ["type: epic"]
|
4 |
-
type: "Feature"
|
5 |
-
body:
|
6 |
-
- type: markdown
|
7 |
-
attributes:
|
8 |
-
value: |
|
9 |
-
**Epic**
|
10 |
-
Use this to define a large, overarching initiative.
|
11 |
-
**Please review [our guide on suggesting enhancements](https://github.com/IFRCGo/go-web-app/blob/develop/CONTRIBUTING.md#suggesting-enhancements).**
|
12 |
-
|
13 |
-
- type: textarea
|
14 |
-
attributes:
|
15 |
-
label: "Epic Summary"
|
16 |
-
description: |
|
17 |
-
Provide a clear and concise summary of the epic.
|
18 |
-
- What is this epic about?
|
19 |
-
- What problem does it solve or what goal does it achieve?
|
20 |
-
- How does it align with the project’s objectives?
|
21 |
-
placeholder: |
|
22 |
-
Example:
|
23 |
-
This epic focuses on implementing a new feature.
|
24 |
-
validations:
|
25 |
-
required: true
|
26 |
-
|
27 |
-
- type: textarea
|
28 |
-
attributes:
|
29 |
-
label: "Additional Context or Resources"
|
30 |
-
description: "Provide any additional information, links, or resources that will help the team understand and execute this epic."
|
31 |
-
placeholder: |
|
32 |
-
Examples:
|
33 |
-
- Link to design mockups: [link]
|
34 |
-
- Technical specs document: [link]
|
35 |
-
- Reference to similar features: [link]
|
36 |
-
validations:
|
37 |
-
required: false
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
go-web-app-develop/.github/ISSUE_TEMPLATE/config.yml
DELETED
@@ -1,5 +0,0 @@
|
|
1 |
-
blank_issues_enabled: true
|
2 |
-
contact_links:
|
3 |
-
- name: Documentation
|
4 |
-
url: https://go-wiki.ifrc.org/en/home
|
5 |
-
about: Please consult the wiki to know more about IFRC GO.
|
|
|
|
|
|
|
|
|
|
|
|
go-web-app-develop/.github/dependabot.yml
DELETED
@@ -1,27 +0,0 @@
|
|
1 |
-
version: 2
|
2 |
-
updates:
|
3 |
-
- package-ecosystem: npm
|
4 |
-
directory: /
|
5 |
-
schedule:
|
6 |
-
interval: weekly
|
7 |
-
groups:
|
8 |
-
eslint:
|
9 |
-
patterns:
|
10 |
-
- "*eslint*"
|
11 |
-
vite:
|
12 |
-
patterns:
|
13 |
-
- "*vite*"
|
14 |
-
postcss:
|
15 |
-
patterns:
|
16 |
-
- "*postcss*"
|
17 |
-
stylelint:
|
18 |
-
patterns:
|
19 |
-
- "*stylelint*"
|
20 |
-
all-other-dependencies:
|
21 |
-
patterns:
|
22 |
-
- "*"
|
23 |
-
exclude-patterns:
|
24 |
-
- "*eslint*"
|
25 |
-
- "*vite*"
|
26 |
-
- "*postcss*"
|
27 |
-
- "*stylelint*"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
go-web-app-develop/.github/pull_request_template.md
DELETED
@@ -1,30 +0,0 @@
|
|
1 |
-
## Summary
|
2 |
-
|
3 |
-
Provide a brief description of what this PR addresses and its purpose.
|
4 |
-
|
5 |
-
## Addresses
|
6 |
-
|
7 |
-
* Issue(s): *List related issues or tickets.*
|
8 |
-
|
9 |
-
## Depends On
|
10 |
-
|
11 |
-
* Other PRs or Dependencies: *List PRs or dependencies this PR relies on.*
|
12 |
-
|
13 |
-
## Changes
|
14 |
-
|
15 |
-
* Detailed list or prose of changes
|
16 |
-
* Breaking changes
|
17 |
-
* Changes to configurations
|
18 |
-
|
19 |
-
## This PR Ensures:
|
20 |
-
|
21 |
-
* \[ ] No typos or grammatical errors
|
22 |
-
* \[ ] No conflict markers left in the code
|
23 |
-
* \[ ] No unwanted comments, temporary files, or auto-generated files
|
24 |
-
* \[ ] No inclusion of secret keys or sensitive data
|
25 |
-
* \[ ] No `console.log` statements meant for debugging
|
26 |
-
* \[ ] All CI checks have passed
|
27 |
-
|
28 |
-
## Additional Notes
|
29 |
-
|
30 |
-
*Optional: Add any other relevant context, screenshots, or details here.*
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
go-web-app-develop/.github/workflows/add-issue-to-backlog.yml
DELETED
@@ -1,16 +0,0 @@
|
|
1 |
-
name: Add issues to Backlog
|
2 |
-
|
3 |
-
on:
|
4 |
-
issues:
|
5 |
-
types:
|
6 |
-
- opened
|
7 |
-
|
8 |
-
jobs:
|
9 |
-
add-to-project:
|
10 |
-
name: Add issue to project
|
11 |
-
runs-on: ubuntu-latest
|
12 |
-
steps:
|
13 |
-
- uses: actions/[email protected]
|
14 |
-
with:
|
15 |
-
project-url: https://github.com/orgs/IFRCGo/projects/12
|
16 |
-
github-token: ${{ secrets.ADD_TO_PROJECT_PAT }}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
go-web-app-develop/.github/workflows/chromatic.yml
DELETED
@@ -1,127 +0,0 @@
|
|
1 |
-
name: 'Chromatic'
|
2 |
-
|
3 |
-
on:
|
4 |
-
pull_request:
|
5 |
-
push:
|
6 |
-
branches:
|
7 |
-
- develop
|
8 |
-
|
9 |
-
concurrency:
|
10 |
-
group: ${{ github.workflow }}-${{ github.ref }}-chromatic
|
11 |
-
cancel-in-progress: true
|
12 |
-
|
13 |
-
permissions:
|
14 |
-
actions: write
|
15 |
-
contents: read
|
16 |
-
pages: write
|
17 |
-
id-token: write
|
18 |
-
|
19 |
-
jobs:
|
20 |
-
changed-files:
|
21 |
-
name: Check for changed files
|
22 |
-
runs-on: ubuntu-latest
|
23 |
-
outputs:
|
24 |
-
all_changed_files: ${{ steps.changed-files.outputs.all_changed_files }}
|
25 |
-
any_changed: ${{ steps.changed-files.outputs.any_changed }}
|
26 |
-
steps:
|
27 |
-
- uses: actions/checkout@v4
|
28 |
-
with:
|
29 |
-
fetch-depth: 0
|
30 |
-
- name: Get changed files
|
31 |
-
id: changed-files
|
32 |
-
uses: tj-actions/changed-files@v44
|
33 |
-
with:
|
34 |
-
files: |
|
35 |
-
packages/ui/**
|
36 |
-
packages/go-ui-storybook/**
|
37 |
-
ui:
|
38 |
-
name: Build UI Library
|
39 |
-
environment: 'test'
|
40 |
-
runs-on: ubuntu-latest
|
41 |
-
needs: [changed-files]
|
42 |
-
if: ${{ needs.changed-files.outputs.any_changed == 'true' }}
|
43 |
-
defaults:
|
44 |
-
run:
|
45 |
-
working-directory: packages/ui
|
46 |
-
steps:
|
47 |
-
- uses: actions/checkout@v4
|
48 |
-
with:
|
49 |
-
fetch-depth: 0
|
50 |
-
- name: Install pnpm
|
51 |
-
uses: pnpm/action-setup@v4
|
52 |
-
- name: Install Node.js
|
53 |
-
uses: actions/setup-node@v4
|
54 |
-
with:
|
55 |
-
node-version: 20
|
56 |
-
cache: 'pnpm'
|
57 |
-
- name: Install dependencies
|
58 |
-
run: pnpm install
|
59 |
-
- name: Typecheck
|
60 |
-
run: pnpm typecheck
|
61 |
-
- name: Lint CSS
|
62 |
-
run: pnpm lint:css
|
63 |
-
- name: Lint JS
|
64 |
-
run: pnpm lint:js
|
65 |
-
- name: build UI library
|
66 |
-
run: pnpm build
|
67 |
-
- uses: actions/upload-artifact@v4
|
68 |
-
with:
|
69 |
-
name: ui-build
|
70 |
-
path: packages/ui/dist
|
71 |
-
chromatic:
|
72 |
-
name: Chromatic Deploy
|
73 |
-
runs-on: ubuntu-latest
|
74 |
-
needs: [ui]
|
75 |
-
steps:
|
76 |
-
- uses: actions/checkout@v4
|
77 |
-
with:
|
78 |
-
fetch-depth: 0
|
79 |
-
- name: Install pnpm
|
80 |
-
uses: pnpm/action-setup@v4
|
81 |
-
- name: Install Node.js
|
82 |
-
uses: actions/setup-node@v4
|
83 |
-
with:
|
84 |
-
node-version: 20
|
85 |
-
cache: 'pnpm'
|
86 |
-
- name: Install dependencies
|
87 |
-
run: pnpm install
|
88 |
-
- uses: actions/download-artifact@v4
|
89 |
-
with:
|
90 |
-
name: ui-build
|
91 |
-
path: packages/ui/dist
|
92 |
-
- name: Run Chromatic
|
93 |
-
uses: chromaui/action@v1
|
94 |
-
with:
|
95 |
-
exitZeroOnChanges: true
|
96 |
-
exitOnceUploaded: true
|
97 |
-
onlyChanged: true
|
98 |
-
skip: "@(renovate/**|dependabot/**)"
|
99 |
-
projectToken: ${{ secrets.CHROMATIC_PROJECT_TOKEN }}
|
100 |
-
token: ${{ secrets.GITHUB_TOKEN }}
|
101 |
-
autoAcceptChanges: "develop"
|
102 |
-
workingDir: packages/go-ui-storybook
|
103 |
-
github-pages:
|
104 |
-
name: Deploy to Github Pages
|
105 |
-
runs-on: ubuntu-latest
|
106 |
-
needs: [ui]
|
107 |
-
steps:
|
108 |
-
- uses: actions/checkout@v4
|
109 |
-
with:
|
110 |
-
fetch-depth: 0
|
111 |
-
- name: Install pnpm
|
112 |
-
uses: pnpm/action-setup@v4
|
113 |
-
- name: Install Node.js
|
114 |
-
uses: actions/setup-node@v4
|
115 |
-
with:
|
116 |
-
node-version: 20
|
117 |
-
cache: 'pnpm'
|
118 |
-
- uses: actions/download-artifact@v4
|
119 |
-
with:
|
120 |
-
name: ui-build
|
121 |
-
path: packages/ui/dist
|
122 |
-
- uses: bitovi/[email protected]
|
123 |
-
with:
|
124 |
-
install_command: pnpm install
|
125 |
-
build_command: pnpm build-storybook
|
126 |
-
path: packages/go-ui-storybook/storybook-static
|
127 |
-
checkout: false
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
go-web-app-develop/.github/workflows/ci.yml
DELETED
@@ -1,304 +0,0 @@
|
|
1 |
-
name: CI
|
2 |
-
|
3 |
-
on:
|
4 |
-
pull_request:
|
5 |
-
push:
|
6 |
-
branches:
|
7 |
-
- 'develop'
|
8 |
-
|
9 |
-
env:
|
10 |
-
APP_ADMIN_URL: ${{ vars.APP_ADMIN_URL }}
|
11 |
-
APP_API_ENDPOINT: ${{ vars.APP_API_ENDPOINT }}
|
12 |
-
APP_ENVIRONMENT: ${{ vars.APP_ENVIRONMENT }}
|
13 |
-
APP_MAPBOX_ACCESS_TOKEN: ${{ vars.APP_MAPBOX_ACCESS_TOKEN }}
|
14 |
-
APP_RISK_ADMIN_URL: ${{ vars.APP_RISK_ADMIN_URL }}
|
15 |
-
APP_RISK_API_ENDPOINT: ${{ vars.APP_RISK_API_ENDPOINT }}
|
16 |
-
APP_SENTRY_DSN: ${{ vars.APP_SENTRY_DSN }}
|
17 |
-
APP_SENTRY_NORMALIZE_DEPTH: ${{ vars.APP_SENTRY_NORMALIZE_DEPTH }}
|
18 |
-
APP_SENTRY_TRACES_SAMPLE_RATE: ${{ vars.APP_SENTRY_TRACES_SAMPLE_RATE }}
|
19 |
-
APP_SHOW_ENV_BANNER: ${{ vars.APP_SHOW_ENV_BANNER }}
|
20 |
-
APP_TINY_API_KEY: ${{ vars.APP_TINY_API_KEY }}
|
21 |
-
APP_TITLE: ${{ vars.APP_TITLE }}
|
22 |
-
GITHUB_WORKFLOW: true
|
23 |
-
|
24 |
-
concurrency:
|
25 |
-
group: ${{ github.workflow }}-${{ github.ref }}
|
26 |
-
cancel-in-progress: true
|
27 |
-
|
28 |
-
jobs:
|
29 |
-
ui:
|
30 |
-
name: Build UI Library
|
31 |
-
environment: 'test'
|
32 |
-
runs-on: ubuntu-latest
|
33 |
-
defaults:
|
34 |
-
run:
|
35 |
-
working-directory: packages/ui
|
36 |
-
steps:
|
37 |
-
- uses: actions/checkout@v4
|
38 |
-
- name: Install pnpm
|
39 |
-
uses: pnpm/action-setup@v4
|
40 |
-
- name: Install Node.js
|
41 |
-
uses: actions/setup-node@v4
|
42 |
-
with:
|
43 |
-
node-version: 20
|
44 |
-
cache: 'pnpm'
|
45 |
-
- name: Install dependencies
|
46 |
-
run: pnpm install
|
47 |
-
|
48 |
-
- name: Typecheck
|
49 |
-
run: pnpm typecheck
|
50 |
-
|
51 |
-
- name: Lint CSS
|
52 |
-
run: pnpm lint:css
|
53 |
-
|
54 |
-
- name: Lint JS
|
55 |
-
run: pnpm lint:js
|
56 |
-
|
57 |
-
- name: Build
|
58 |
-
run: pnpm build
|
59 |
-
|
60 |
-
- uses: actions/upload-artifact@v4
|
61 |
-
with:
|
62 |
-
name: ui-build
|
63 |
-
path: packages/ui/dist
|
64 |
-
|
65 |
-
test:
|
66 |
-
name: Run tests
|
67 |
-
environment: 'test'
|
68 |
-
runs-on: ubuntu-latest
|
69 |
-
defaults:
|
70 |
-
run:
|
71 |
-
working-directory: app
|
72 |
-
needs: [ui]
|
73 |
-
steps:
|
74 |
-
- uses: actions/checkout@v4
|
75 |
-
- name: Install pnpm
|
76 |
-
uses: pnpm/action-setup@v4
|
77 |
-
- name: Install Node.js
|
78 |
-
uses: actions/setup-node@v4
|
79 |
-
with:
|
80 |
-
node-version: 20
|
81 |
-
cache: 'pnpm'
|
82 |
-
- name: Install dependencies
|
83 |
-
run: pnpm install
|
84 |
-
|
85 |
-
- uses: actions/download-artifact@v4
|
86 |
-
with:
|
87 |
-
name: ui-build
|
88 |
-
path: packages/ui/dist
|
89 |
-
|
90 |
-
- name: Run test
|
91 |
-
run: pnpm test
|
92 |
-
|
93 |
-
translation:
|
94 |
-
continue-on-error: true
|
95 |
-
name: Identify error with translation files
|
96 |
-
runs-on: ubuntu-latest
|
97 |
-
defaults:
|
98 |
-
run:
|
99 |
-
working-directory: app
|
100 |
-
needs: [ui]
|
101 |
-
steps:
|
102 |
-
- uses: actions/checkout@v4
|
103 |
-
- name: Install pnpm
|
104 |
-
uses: pnpm/action-setup@v4
|
105 |
-
- name: Install Node.js
|
106 |
-
uses: actions/setup-node@v4
|
107 |
-
with:
|
108 |
-
node-version: 20
|
109 |
-
cache: 'pnpm'
|
110 |
-
- name: Install dependencies
|
111 |
-
run: pnpm install
|
112 |
-
|
113 |
-
- uses: actions/download-artifact@v4
|
114 |
-
with:
|
115 |
-
name: ui-build
|
116 |
-
path: packages/ui/dist
|
117 |
-
|
118 |
-
- name: Identify error with translation files
|
119 |
-
run: pnpm lint:translation
|
120 |
-
|
121 |
-
translation-migrations:
|
122 |
-
if: |
|
123 |
-
(github.event_name == 'pull_request' && github.base_ref == 'develop') ||
|
124 |
-
(github.event_name == 'push' && github.ref == 'refs/heads/develop')
|
125 |
-
continue-on-error: true
|
126 |
-
name: Identify if translation migrations need to be generated
|
127 |
-
runs-on: ubuntu-latest
|
128 |
-
defaults:
|
129 |
-
run:
|
130 |
-
working-directory: app
|
131 |
-
needs: [ui]
|
132 |
-
steps:
|
133 |
-
- uses: actions/checkout@v4
|
134 |
-
- name: Install pnpm
|
135 |
-
uses: pnpm/action-setup@v4
|
136 |
-
- name: Install Node.js
|
137 |
-
uses: actions/setup-node@v4
|
138 |
-
with:
|
139 |
-
node-version: 20
|
140 |
-
cache: 'pnpm'
|
141 |
-
- name: Install dependencies
|
142 |
-
run: pnpm install
|
143 |
-
|
144 |
-
- uses: actions/download-artifact@v4
|
145 |
-
with:
|
146 |
-
name: ui-build
|
147 |
-
path: packages/ui/dist
|
148 |
-
|
149 |
-
- name: Identify if translation migrations need to be generated
|
150 |
-
run: |
|
151 |
-
if pnpm translatte:generate; then
|
152 |
-
# The step should fail if generation is possible
|
153 |
-
exit 1
|
154 |
-
fi
|
155 |
-
|
156 |
-
unused:
|
157 |
-
name: Identify unused files
|
158 |
-
runs-on: ubuntu-latest
|
159 |
-
needs: [ui]
|
160 |
-
steps:
|
161 |
-
- uses: actions/checkout@v4
|
162 |
-
- name: Install pnpm
|
163 |
-
uses: pnpm/action-setup@v4
|
164 |
-
- name: Install Node.js
|
165 |
-
uses: actions/setup-node@v4
|
166 |
-
with:
|
167 |
-
node-version: 20
|
168 |
-
cache: 'pnpm'
|
169 |
-
- name: Install dependencies
|
170 |
-
run: pnpm install
|
171 |
-
|
172 |
-
- name: Initialize types
|
173 |
-
run: pnpm initialize:type
|
174 |
-
working-directory: app
|
175 |
-
|
176 |
-
- name: Identify unused files
|
177 |
-
run: pnpm lint:unused
|
178 |
-
|
179 |
-
lint:
|
180 |
-
name: Lint JS
|
181 |
-
runs-on: ubuntu-latest
|
182 |
-
defaults:
|
183 |
-
run:
|
184 |
-
working-directory: app
|
185 |
-
needs: [ui]
|
186 |
-
steps:
|
187 |
-
- uses: actions/checkout@v4
|
188 |
-
- name: Install pnpm
|
189 |
-
uses: pnpm/action-setup@v4
|
190 |
-
- name: Install Node.js
|
191 |
-
uses: actions/setup-node@v4
|
192 |
-
with:
|
193 |
-
node-version: 20
|
194 |
-
cache: 'pnpm'
|
195 |
-
- name: Install dependencies
|
196 |
-
run: pnpm install
|
197 |
-
|
198 |
-
- uses: actions/download-artifact@v4
|
199 |
-
with:
|
200 |
-
name: ui-build
|
201 |
-
path: packages/ui/dist
|
202 |
-
|
203 |
-
- name: Lint JS
|
204 |
-
run: pnpm lint:js
|
205 |
-
|
206 |
-
lint-css:
|
207 |
-
name: Lint CSS
|
208 |
-
runs-on: ubuntu-latest
|
209 |
-
defaults:
|
210 |
-
run:
|
211 |
-
working-directory: app
|
212 |
-
needs: [ui]
|
213 |
-
steps:
|
214 |
-
- uses: actions/checkout@v4
|
215 |
-
- name: Install pnpm
|
216 |
-
uses: pnpm/action-setup@v4
|
217 |
-
- name: Install Node.js
|
218 |
-
uses: actions/setup-node@v4
|
219 |
-
with:
|
220 |
-
node-version: 20
|
221 |
-
cache: 'pnpm'
|
222 |
-
- name: Install dependencies
|
223 |
-
run: pnpm install
|
224 |
-
|
225 |
-
- uses: actions/download-artifact@v4
|
226 |
-
with:
|
227 |
-
name: ui-build
|
228 |
-
path: packages/ui/dist
|
229 |
-
|
230 |
-
- name: Lint CSS
|
231 |
-
run: pnpm lint:css
|
232 |
-
|
233 |
-
# FIXME: Identify a way to generate schema before we run typecheck
|
234 |
-
# typecheck:
|
235 |
-
# name: Typecheck
|
236 |
-
# runs-on: ubuntu-latest
|
237 |
-
# steps:
|
238 |
-
# - uses: actions/checkout@v4
|
239 |
-
# - name: Install pnpm
|
240 |
-
# uses: pnpm/action-setup@v4
|
241 |
-
# - name: Install Node.js
|
242 |
-
# uses: actions/setup-node@v4
|
243 |
-
# with:
|
244 |
-
# node-version: 20
|
245 |
-
# cache: 'pnpm'
|
246 |
-
# - name: Install dependencies
|
247 |
-
# run: pnpm install
|
248 |
-
#
|
249 |
-
# - name: Typecheck
|
250 |
-
# run: pnpm typecheck
|
251 |
-
|
252 |
-
typos:
|
253 |
-
name: Spell Check with Typos
|
254 |
-
runs-on: ubuntu-latest
|
255 |
-
steps:
|
256 |
-
- name: Checkout Actions Repository
|
257 |
-
uses: actions/checkout@v4
|
258 |
-
|
259 |
-
- name: Check spelling
|
260 |
-
uses: crate-ci/[email protected]
|
261 |
-
|
262 |
-
build:
|
263 |
-
name: Build GO Web App
|
264 |
-
environment: 'test'
|
265 |
-
runs-on: ubuntu-latest
|
266 |
-
defaults:
|
267 |
-
run:
|
268 |
-
working-directory: app
|
269 |
-
needs: [lint, lint-css, test, ui]
|
270 |
-
steps:
|
271 |
-
- uses: actions/checkout@v4
|
272 |
-
- name: Install pnpm
|
273 |
-
uses: pnpm/action-setup@v4
|
274 |
-
- name: Install Node.js
|
275 |
-
uses: actions/setup-node@v4
|
276 |
-
with:
|
277 |
-
node-version: 20
|
278 |
-
cache: 'pnpm'
|
279 |
-
- name: Install dependencies
|
280 |
-
run: pnpm install
|
281 |
-
|
282 |
-
- uses: actions/download-artifact@v4
|
283 |
-
with:
|
284 |
-
name: ui-build
|
285 |
-
path: packages/ui/dist
|
286 |
-
|
287 |
-
- name: Build
|
288 |
-
run: pnpm build
|
289 |
-
|
290 |
-
validate_helm:
|
291 |
-
name: Validate Helm
|
292 |
-
runs-on: ubuntu-latest
|
293 |
-
|
294 |
-
steps:
|
295 |
-
- uses: actions/checkout@main
|
296 |
-
|
297 |
-
- name: Install Helm
|
298 |
-
uses: azure/setup-helm@v4
|
299 |
-
|
300 |
-
- name: Helm lint
|
301 |
-
run: helm lint ./nginx-serve/helm --values ./nginx-serve/helm/values-test.yaml
|
302 |
-
|
303 |
-
- name: Helm template
|
304 |
-
run: helm template ./nginx-serve/helm --values ./nginx-serve/helm/values-test.yaml
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
go-web-app-develop/.github/workflows/publish-nginx-serve.yml
DELETED
@@ -1,147 +0,0 @@
|
|
1 |
-
name: Publish Helm
|
2 |
-
|
3 |
-
on:
|
4 |
-
workflow_dispatch:
|
5 |
-
push:
|
6 |
-
branches:
|
7 |
-
- develop
|
8 |
-
- project/*
|
9 |
-
|
10 |
-
permissions:
|
11 |
-
packages: write
|
12 |
-
|
13 |
-
|
14 |
-
jobs:
|
15 |
-
publish_image:
|
16 |
-
name: Publish Docker Image
|
17 |
-
runs-on: ubuntu-latest
|
18 |
-
|
19 |
-
outputs:
|
20 |
-
docker_image_name: ${{ steps.prep.outputs.tagged_image_name }}
|
21 |
-
docker_image_tag: ${{ steps.prep.outputs.tag }}
|
22 |
-
docker_image: ${{ steps.prep.outputs.tagged_image }}
|
23 |
-
|
24 |
-
steps:
|
25 |
-
- uses: actions/checkout@main
|
26 |
-
|
27 |
-
- name: Login to GitHub Container Registry
|
28 |
-
uses: docker/login-action@v3
|
29 |
-
with:
|
30 |
-
registry: ghcr.io
|
31 |
-
username: ${{ github.actor }}
|
32 |
-
password: ${{ secrets.GITHUB_TOKEN }}
|
33 |
-
|
34 |
-
- name: 🐳 Prepare Docker
|
35 |
-
id: prep
|
36 |
-
env:
|
37 |
-
IMAGE_NAME: ghcr.io/${{ github.repository }}
|
38 |
-
run: |
|
39 |
-
BRANCH_NAME=$(echo $GITHUB_REF_NAME | sed 's|[/:]|-|' | tr '[:upper:]' '[:lower:]' | sed 's/_/-/g' | cut -c1-100 | sed 's/-*$//')
|
40 |
-
|
41 |
-
# XXX: Check if there is a slash in the BRANCH_NAME eg: project/add-docker
|
42 |
-
if [[ "$BRANCH_NAME" == *"/"* ]]; then
|
43 |
-
# XXX: Change the docker image package to -alpha
|
44 |
-
IMAGE_NAME="$IMAGE_NAME-alpha"
|
45 |
-
TAG="$(echo "$BRANCH_NAME" | sed 's|/|-|g').$(echo $GITHUB_SHA | head -c7)"
|
46 |
-
else
|
47 |
-
TAG="$BRANCH_NAME.$(echo $GITHUB_SHA | head -c7)"
|
48 |
-
fi
|
49 |
-
|
50 |
-
IMAGE_NAME=$(echo $IMAGE_NAME | tr '[:upper:]' '[:lower:]')
|
51 |
-
echo "tagged_image_name=${IMAGE_NAME}" >> $GITHUB_OUTPUT
|
52 |
-
echo "tag=${TAG}" >> $GITHUB_OUTPUT
|
53 |
-
echo "tagged_image=${IMAGE_NAME}:${TAG}" >> $GITHUB_OUTPUT
|
54 |
-
echo "::notice::Tagged docker image: ${IMAGE_NAME}:${TAG}"
|
55 |
-
|
56 |
-
- name: 🐳 Set up Docker Buildx
|
57 |
-
id: buildx
|
58 |
-
uses: docker/setup-buildx-action@v3
|
59 |
-
|
60 |
-
- name: 🐳 Cache Docker layers
|
61 |
-
uses: actions/cache@v4
|
62 |
-
with:
|
63 |
-
path: /tmp/.buildx-cache
|
64 |
-
key: ${{ runner.os }}-buildx-${{ github.ref }}
|
65 |
-
restore-keys: |
|
66 |
-
${{ runner.os }}-buildx-refs/develop
|
67 |
-
${{ runner.os }}-buildx-
|
68 |
-
|
69 |
-
- name: 🐳 Docker build
|
70 |
-
uses: docker/build-push-action@v6
|
71 |
-
with:
|
72 |
-
context: .
|
73 |
-
builder: ${{ steps.buildx.outputs.name }}
|
74 |
-
file: nginx-serve/Dockerfile
|
75 |
-
target: nginx-serve
|
76 |
-
load: true
|
77 |
-
push: true
|
78 |
-
tags: ${{ steps.prep.outputs.tagged_image }}
|
79 |
-
cache-from: type=local,src=/tmp/.buildx-cache
|
80 |
-
cache-to: type=local,dest=/tmp/.buildx-cache-new
|
81 |
-
build-args: |
|
82 |
-
"APP_SENTRY_TRACES_SAMPLE_RATE=0.8"
|
83 |
-
"APP_SENTRY_REPLAYS_SESSION_SAMPLE_RATE=0.8"
|
84 |
-
"APP_SENTRY_REPLAYS_ON_ERROR_SAMPLE_RATE=0.8"
|
85 |
-
|
86 |
-
- name: 🐳 Move docker cache
|
87 |
-
run: |
|
88 |
-
rm -rf /tmp/.buildx-cache
|
89 |
-
mv /tmp/.buildx-cache-new /tmp/.buildx-cache
|
90 |
-
|
91 |
-
publish_helm:
|
92 |
-
name: Publish Helm
|
93 |
-
needs: publish_image
|
94 |
-
runs-on: ubuntu-latest
|
95 |
-
|
96 |
-
steps:
|
97 |
-
- name: Checkout code
|
98 |
-
uses: actions/checkout@v4
|
99 |
-
|
100 |
-
- name: Login to GitHub Container Registry
|
101 |
-
uses: docker/login-action@v3
|
102 |
-
with:
|
103 |
-
registry: ghcr.io
|
104 |
-
username: ${{ github.actor }}
|
105 |
-
password: ${{ secrets.GITHUB_TOKEN }}
|
106 |
-
|
107 |
-
- name: Install Helm
|
108 |
-
uses: azure/setup-helm@v3
|
109 |
-
|
110 |
-
- name: Tag docker image in Helm Chart values.yaml
|
111 |
-
env:
|
112 |
-
IMAGE_NAME: ${{ needs.publish_image.outputs.docker_image_name }}
|
113 |
-
IMAGE_TAG: ${{ needs.publish_image.outputs.docker_image_tag }}
|
114 |
-
run: |
|
115 |
-
# Update values.yaml with latest docker image
|
116 |
-
sed -i "s|SET-BY-CICD-IMAGE|$IMAGE_NAME|" nginx-serve/helm/values.yaml
|
117 |
-
sed -i "s/SET-BY-CICD-TAG/$IMAGE_TAG/" nginx-serve/helm/values.yaml
|
118 |
-
|
119 |
-
- name: Package Helm Chart
|
120 |
-
id: set-variables
|
121 |
-
run: |
|
122 |
-
# XXX: Check if there is a slash in the BRANCH_NAME eg: project/add-docker
|
123 |
-
if [[ "$GITHUB_REF_NAME" == *"/"* ]]; then
|
124 |
-
# XXX: Change the helm chart to <chart-name>-alpha
|
125 |
-
sed -i 's/^name: \(.*\)/name: \1-alpha/' nginx-serve/helm/Chart.yaml
|
126 |
-
fi
|
127 |
-
|
128 |
-
SHA_SHORT=$(git rev-parse --short HEAD)
|
129 |
-
sed -i "s/SET-BY-CICD/$SHA_SHORT/g" nginx-serve/helm/Chart.yaml
|
130 |
-
helm package ./nginx-serve/helm -d .helm-charts
|
131 |
-
|
132 |
-
- name: Push Helm Chart
|
133 |
-
env:
|
134 |
-
IMAGE: ${{ needs.publish_image.outputs.docker_image }}
|
135 |
-
OCI_REPO: oci://ghcr.io/${{ github.repository }}
|
136 |
-
run: |
|
137 |
-
OCI_REPO=$(echo $OCI_REPO | tr '[:upper:]' '[:lower:]')
|
138 |
-
PACKAGE_FILE=$(ls .helm-charts/*.tgz | head -n 1)
|
139 |
-
echo "# Helm Chart" >> $GITHUB_STEP_SUMMARY
|
140 |
-
echo "" >> $GITHUB_STEP_SUMMARY
|
141 |
-
echo "Tagged Image: **$IMAGE**" >> $GITHUB_STEP_SUMMARY
|
142 |
-
echo "" >> $GITHUB_STEP_SUMMARY
|
143 |
-
echo "Helm push output" >> $GITHUB_STEP_SUMMARY
|
144 |
-
echo "" >> $GITHUB_STEP_SUMMARY
|
145 |
-
echo '```bash' >> $GITHUB_STEP_SUMMARY
|
146 |
-
helm push "$PACKAGE_FILE" $OCI_REPO >> $GITHUB_STEP_SUMMARY
|
147 |
-
echo '```' >> $GITHUB_STEP_SUMMARY
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
go-web-app-develop/.github/workflows/publish-storybook-nginx-serve.yml
DELETED
@@ -1,127 +0,0 @@
|
|
1 |
-
name: Publish Storybook Helm
|
2 |
-
|
3 |
-
on:
|
4 |
-
workflow_dispatch:
|
5 |
-
push:
|
6 |
-
branches:
|
7 |
-
- develop
|
8 |
-
|
9 |
-
permissions:
|
10 |
-
packages: write
|
11 |
-
|
12 |
-
|
13 |
-
jobs:
|
14 |
-
publish_image:
|
15 |
-
name: 🐳 Publish Docker Image
|
16 |
-
runs-on: ubuntu-latest
|
17 |
-
|
18 |
-
outputs:
|
19 |
-
docker_image_name: ${{ steps.prep.outputs.tagged_image_name }}
|
20 |
-
docker_image_tag: ${{ steps.prep.outputs.tag }}
|
21 |
-
docker_image: ${{ steps.prep.outputs.tagged_image }}
|
22 |
-
|
23 |
-
steps:
|
24 |
-
- uses: actions/checkout@main
|
25 |
-
|
26 |
-
- name: Login to GitHub Container Registry
|
27 |
-
uses: docker/login-action@v3
|
28 |
-
with:
|
29 |
-
registry: ghcr.io
|
30 |
-
username: ${{ github.actor }}
|
31 |
-
password: ${{ secrets.GITHUB_TOKEN }}
|
32 |
-
|
33 |
-
- name: 🐳 Prepare Docker
|
34 |
-
id: prep
|
35 |
-
env:
|
36 |
-
IMAGE_NAME: ghcr.io/${{ github.repository }}/go-ui-storybook
|
37 |
-
run: |
|
38 |
-
BRANCH_NAME=$(echo $GITHUB_REF_NAME | sed 's|[/:]|-|' | tr '[:upper:]' '[:lower:]' | sed 's/_/-/g' | cut -c1-100 | sed 's/-*$//')
|
39 |
-
TAG="$BRANCH_NAME.$(echo $GITHUB_SHA | head -c7)"
|
40 |
-
IMAGE_NAME=$(echo $IMAGE_NAME | tr '[:upper:]' '[:lower:]')
|
41 |
-
echo "tagged_image_name=${IMAGE_NAME}" >> $GITHUB_OUTPUT
|
42 |
-
echo "tag=${TAG}" >> $GITHUB_OUTPUT
|
43 |
-
echo "tagged_image=${IMAGE_NAME}:${TAG}" >> $GITHUB_OUTPUT
|
44 |
-
echo "::notice::Tagged docker image: ${IMAGE_NAME}:${TAG}"
|
45 |
-
|
46 |
-
- name: 🐳 Set up Docker Buildx
|
47 |
-
id: buildx
|
48 |
-
uses: docker/setup-buildx-action@v3
|
49 |
-
|
50 |
-
- name: 🐳 Cache Docker layers
|
51 |
-
uses: actions/cache@v4
|
52 |
-
with:
|
53 |
-
path: /tmp/.buildx-cache
|
54 |
-
key: ${{ runner.os }}-buildx-${{ github.ref }}
|
55 |
-
restore-keys: |
|
56 |
-
${{ runner.os }}-buildx-refs/develop
|
57 |
-
${{ runner.os }}-buildx-
|
58 |
-
|
59 |
-
- name: 🐳 Docker build
|
60 |
-
uses: docker/build-push-action@v6
|
61 |
-
with:
|
62 |
-
context: .
|
63 |
-
builder: ${{ steps.buildx.outputs.name }}
|
64 |
-
file: packages/go-ui-storybook/nginx-serve/Dockerfile
|
65 |
-
target: nginx-serve
|
66 |
-
load: true
|
67 |
-
push: true
|
68 |
-
tags: ${{ steps.prep.outputs.tagged_image }}
|
69 |
-
cache-from: type=local,src=/tmp/.buildx-cache
|
70 |
-
cache-to: type=local,dest=/tmp/.buildx-cache-new
|
71 |
-
|
72 |
-
- name: 🐳 Move docker cache
|
73 |
-
run: |
|
74 |
-
rm -rf /tmp/.buildx-cache
|
75 |
-
mv /tmp/.buildx-cache-new /tmp/.buildx-cache
|
76 |
-
|
77 |
-
publish_helm:
|
78 |
-
name: ⎈ Publish Helm
|
79 |
-
needs: publish_image
|
80 |
-
runs-on: ubuntu-latest
|
81 |
-
|
82 |
-
steps:
|
83 |
-
- name: Checkout code
|
84 |
-
uses: actions/checkout@v4
|
85 |
-
|
86 |
-
- name: Login to GitHub Container Registry
|
87 |
-
uses: docker/login-action@v3
|
88 |
-
with:
|
89 |
-
registry: ghcr.io
|
90 |
-
username: ${{ github.actor }}
|
91 |
-
password: ${{ secrets.GITHUB_TOKEN }}
|
92 |
-
|
93 |
-
- name: ⎈ Install Helm
|
94 |
-
uses: azure/setup-helm@v3
|
95 |
-
|
96 |
-
- name: ⎈ Tag docker image in Helm Chart values.yaml
|
97 |
-
env:
|
98 |
-
IMAGE_NAME: ${{ needs.publish_image.outputs.docker_image_name }}
|
99 |
-
IMAGE_TAG: ${{ needs.publish_image.outputs.docker_image_tag }}
|
100 |
-
run: |
|
101 |
-
# Update values.yaml with latest docker image
|
102 |
-
sed -i "s|SET-BY-CICD-IMAGE|$IMAGE_NAME|" packages/go-ui-storybook/nginx-serve/helm/values.yaml
|
103 |
-
sed -i "s/SET-BY-CICD-TAG/$IMAGE_TAG/" packages/go-ui-storybook/nginx-serve/helm/values.yaml
|
104 |
-
|
105 |
-
- name: ⎈ Package Helm Chart
|
106 |
-
id: set-variables
|
107 |
-
run: |
|
108 |
-
SHA_SHORT=$(git rev-parse --short HEAD)
|
109 |
-
sed -i "s/SET-BY-CICD/$SHA_SHORT/g" packages/go-ui-storybook/nginx-serve/helm/Chart.yaml
|
110 |
-
helm package ./packages/go-ui-storybook/nginx-serve/helm -d .helm-charts
|
111 |
-
|
112 |
-
- name: ⎈ Push Helm Chart
|
113 |
-
env:
|
114 |
-
IMAGE: ${{ needs.publish_image.outputs.docker_image }}
|
115 |
-
OCI_REPO: oci://ghcr.io/${{ github.repository }}
|
116 |
-
run: |
|
117 |
-
OCI_REPO=$(echo $OCI_REPO | tr '[:upper:]' '[:lower:]')
|
118 |
-
PACKAGE_FILE=$(ls .helm-charts/*.tgz | head -n 1)
|
119 |
-
echo "## 🚀 IFRC GO UI Helm Chart 🚀" >> $GITHUB_STEP_SUMMARY
|
120 |
-
echo "" >> $GITHUB_STEP_SUMMARY
|
121 |
-
echo "🐳 Tagged Image: **$IMAGE**" >> $GITHUB_STEP_SUMMARY
|
122 |
-
echo "" >> $GITHUB_STEP_SUMMARY
|
123 |
-
echo "⎈ Helm push output" >> $GITHUB_STEP_SUMMARY
|
124 |
-
echo "" >> $GITHUB_STEP_SUMMARY
|
125 |
-
echo '```bash' >> $GITHUB_STEP_SUMMARY
|
126 |
-
helm push "$PACKAGE_FILE" $OCI_REPO >> $GITHUB_STEP_SUMMARY
|
127 |
-
echo '```' >> $GITHUB_STEP_SUMMARY
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
go-web-app-develop/.gitignore
DELETED
@@ -1,43 +0,0 @@
|
|
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 |
-
node_modules
|
11 |
-
dist
|
12 |
-
dist-ssr
|
13 |
-
build
|
14 |
-
build-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 |
-
|
28 |
-
.env*
|
29 |
-
!.env.example
|
30 |
-
.eslintcache
|
31 |
-
tsconfig.tsbuildinfo
|
32 |
-
|
33 |
-
# Custom ignores
|
34 |
-
|
35 |
-
stats.html
|
36 |
-
generated/
|
37 |
-
coverage/
|
38 |
-
|
39 |
-
# storybook build
|
40 |
-
storybook-static/
|
41 |
-
|
42 |
-
# Helm
|
43 |
-
.helm-charts/
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
go-web-app-develop/.npmrc
DELETED
@@ -1 +0,0 @@
|
|
1 |
-
enable-pre-post-scripts=true
|
|
|
|
go-web-app-develop/COLLABORATING.md
DELETED
@@ -1,18 +0,0 @@
|
|
1 |
-
# IFRC GO Collaboration Guide
|
2 |
-
|
3 |
-
This document offers guidelines for collaborators on codebase maintenance, testing, building and deployment, and issue management.
|
4 |
-
|
5 |
-
## Repository
|
6 |
-
|
7 |
-
* [Issues and Pull Requests](./collaborating/issues-and-pull-requests.md)
|
8 |
-
* [Structure](./collaborating/repository-structure.md)
|
9 |
-
* [Linting](./collaborating/linting.md)
|
10 |
-
* [Technology Used](./collaborating/technology.md)
|
11 |
-
|
12 |
-
## Development
|
13 |
-
|
14 |
-
* [Developing](./collaborating/developing.md)
|
15 |
-
* [Translation](./collaborating/translation.md)
|
16 |
-
* [Building](./collaborating/building.md)
|
17 |
-
* [Testing](./collaborating/testing.md)
|
18 |
-
* [Release](./collaborating/release.md)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
go-web-app-develop/CONTRIBUTING.md
DELETED
@@ -1,81 +0,0 @@
|
|
1 |
-
# IFRC GO Web Application Contributing Guide
|
2 |
-
|
3 |
-
First off, thanks for taking the time to contribute! ❤️
|
4 |
-
|
5 |
-
All types of contributions are encouraged and valued. See the [Table of Contents](#table-of-contents) for different ways to help and details about how this project handles them. Please make sure to read the relevant section before making your contribution.
|
6 |
-
|
7 |
-
## Table of Contents
|
8 |
-
|
9 |
-
* [I Have a Question](#i-have-a-question)
|
10 |
-
* [I Want To Contribute](#i-want-to-contribute)
|
11 |
-
* [What should I know before I get started?](#what-should-i-know-before-i-get-started)
|
12 |
-
* [Reporting Bugs](#reporting-bugs)
|
13 |
-
* [Suggesting Enhancements](#suggesting-enhancements)
|
14 |
-
* [Becoming a Collaborator](#becoming-a-collaborator)
|
15 |
-
|
16 |
-
## I Have a Question
|
17 |
-
|
18 |
-
> If you want to ask a question, we assume that you have read the available [documentation](https://go-wiki.ifrc.org/en/home).
|
19 |
-
|
20 |
-
Before you ask a question, it is best to search for existing [issues](https://github.com/IFRCGo/go-web-app/issues) that might help you. In case you have found a suitable issue and still need clarification, you can write your question in this issue.
|
21 |
-
|
22 |
-
If you then still feel the need to ask a question and need clarification, we recommend the following:
|
23 |
-
|
24 |
-
* Open a [discussion](https://github.com/IFRCGo/go-web-app/discussions).
|
25 |
-
* Open an [issue](https://github.com/IFRCGo/go-web-app/issues/new/choose).
|
26 |
-
* Provide as much context as you can about what you're running into.
|
27 |
-
|
28 |
-
## I Want To Contribute
|
29 |
-
|
30 |
-
Any individual is welcome to contribute to IFRC GO. The repository currently has two kinds of contribution personas:
|
31 |
-
|
32 |
-
* A **Contributor** is any individual who creates an issue/PR, comments on an issue/PR, or contributes in some other way.
|
33 |
-
* A **Collaborator** is a contributor with write access to the repository.
|
34 |
-
|
35 |
-
### What should I know before I get started?
|
36 |
-
|
37 |
-
### IFRC GO and Packages
|
38 |
-
|
39 |
-
The project is hosted at <https://go.ifrc.org/>.
|
40 |
-
|
41 |
-
The project comprises several [repositories](https://github.com/orgs/IFRCGo/repositories), with notable ones including:
|
42 |
-
|
43 |
-
* [go-web-app](https://github.com/IFRCGo/go-web-app/) - The frontend repository for the IFRC GO project.
|
44 |
-
* [go-api](https://github.com/IFRCGo/go-api) - The backed repository for the IFRC GO project.
|
45 |
-
|
46 |
-
### Reporting Bugs
|
47 |
-
|
48 |
-
#### Before Submitting a Bug Report
|
49 |
-
|
50 |
-
Ensure the issue is not a user error by reviewing the documentation. Check the [existing bug reports](https://github.com/IFRCGo/go-web-app/issues?q=is%3Aissue%20state%3Aopen%20type%3ABug) to confirm if the issue has already been reported.
|
51 |
-
|
52 |
-
#### Submitting the Bug Report
|
53 |
-
|
54 |
-
1. Open a new [Issue](https://github.com/IFRCGo/go-web-app/issues/new?q=is%3Aissue+state%3Aopen+type%3ABug\&template=01_bug_report.yml).
|
55 |
-
2. Provide all relevant details.
|
56 |
-
|
57 |
-
#### After Submitting the Issue
|
58 |
-
|
59 |
-
* The team will categorize and attempt to reproduce the issue.
|
60 |
-
* If reproducible, the team will work on resolving the bug.
|
61 |
-
|
62 |
-
### Suggesting Enhancements
|
63 |
-
|
64 |
-
#### Before Submitting an Enhancement
|
65 |
-
|
66 |
-
* Review the [documentation](https://go-wiki.ifrc.org/en/home) to ensure the functionality isn't already covered.
|
67 |
-
* Perform a [search](https://github.com/IFRCGo/go-web-app/issues) to check if the enhancement has been suggested. If so, comment on the existing issue.
|
68 |
-
* Confirm that your suggestion aligns with the project’s scope and objectives.
|
69 |
-
|
70 |
-
#### How to Submit an Enhancement Suggestion
|
71 |
-
|
72 |
-
Enhancements are tracked as [GitHub issues](https://github.com/IFRCGo/go-web-app/issues).
|
73 |
-
|
74 |
-
* Open a new [feature request](https://github.com/IFRCGo/go-web-app/issues/new?q=is%3Aissue+state%3Aopen+type%3ABug\&template=02_feature_request.yml) or [Epic ticket](https://github.com/IFRCGo/go-web-app/issues/new?q=is%3Aissue+state%3Aopen+type%3ABug\&template=03_epic_request.yml) depending on the scale of the enhancement.
|
75 |
-
* Provide a clear description and submit the ticket.
|
76 |
-
|
77 |
-
## Becoming a Collaborator
|
78 |
-
|
79 |
-
Collaborators are key members of the IFRC GO Web Application Team, responsible for its development. Members should have expertise in modern web technologies and standards.
|
80 |
-
|
81 |
-
For detailed guidelines, refer to the [Collaboration Guide](./COLLABORATING.md).
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
go-web-app-develop/LICENSE
DELETED
@@ -1,21 +0,0 @@
|
|
1 |
-
MIT License
|
2 |
-
|
3 |
-
Copyright (c) 2023 GO
|
4 |
-
|
5 |
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6 |
-
of this software and associated documentation files (the "Software"), to deal
|
7 |
-
in the Software without restriction, including without limitation the rights
|
8 |
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9 |
-
copies of the Software, and to permit persons to whom the Software is
|
10 |
-
furnished to do so, subject to the following conditions:
|
11 |
-
|
12 |
-
The above copyright notice and this permission notice shall be included in all
|
13 |
-
copies or substantial portions of the Software.
|
14 |
-
|
15 |
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16 |
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17 |
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18 |
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19 |
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20 |
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21 |
-
SOFTWARE.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
go-web-app-develop/README.md
DELETED
@@ -1,117 +0,0 @@
|
|
1 |
-
<p align="center">
|
2 |
-
<br />
|
3 |
-
<a href="https://go.ifrc.org/">
|
4 |
-
<picture>
|
5 |
-
<img src="https://github.com/IFRCGo/go-web-app/blob/develop/app/src/assets/icons/go-logo-2020.svg" width="200px" alt="IFRC GO Logo">
|
6 |
-
</picture>
|
7 |
-
</a>
|
8 |
-
</p>
|
9 |
-
|
10 |
-
# IFRC GO
|
11 |
-
|
12 |
-
[IFRC GO](https://go.ifrc.org/) is the platform of the International Federation of Red Cross and Red Crescent, aimed at connecting crucial information on emergency needs with the appropriate response. This repository houses the frontend source code for the application, developed using [React](https://react.dev/), [Vite](https://vitejs.dev/), and associated technologies.
|
13 |
-
|
14 |
-
## Built With
|
15 |
-
|
16 |
-
[![React][react-shields]][react-url] [![Vite][vite-shields]][vite-url] [![TypeScript][typescript-shields]][typescript-url] [![pnpm][pnpm-shields]][pnpm-url]
|
17 |
-
|
18 |
-
## Getting Started
|
19 |
-
|
20 |
-
Below are the steps to guide you through preparing your local environment for IFRC GO Web application development. The repository is set up as a [monorepo](https://monorepo.tools/). The [app](https://github.com/IFRCGo/go-web-app/tree/develop/app) directory houses the application code, while the [packages](https://github.com/IFRCGo/go-web-app/tree/develop/packages) directory contains related packages, including the [IFRC GO UI](https://www.npmjs.com/package/@ifrc-go/ui) components library.
|
21 |
-
|
22 |
-
### Prerequisites
|
23 |
-
|
24 |
-
To begin, ensure you have network access. Then, you'll need the following:
|
25 |
-
|
26 |
-
1. [Git](https://git-scm.com/)
|
27 |
-
2. [Node.js](https://nodejs.org/en/) as specified under `engines` section in `package.json` file
|
28 |
-
3. [pnpm](https://pnpm.io/) as specified under `engines` section in `package.json` file
|
29 |
-
4. Alternatively, you can use [Docker](https://www.docker.com/) to build the application.
|
30 |
-
|
31 |
-
> \[!NOTE]\
|
32 |
-
> Make sure the correct versions of pnpm and Node.js are installed. They are specified under `engines` section in `package.json` file.
|
33 |
-
|
34 |
-
### Local Development
|
35 |
-
|
36 |
-
1. Clone the repository using HTTPS, SSH, or GitHub CLI:
|
37 |
-
|
38 |
-
```bash
|
39 |
-
git clone https://github.com/IFRCGo/go-web-app.git # HTTPS
|
40 |
-
git clone [email protected]:IFRCGo/go-web-app.git # SSH
|
41 |
-
gh repo clone IFRCGo/go-web-app # GitHub CLI
|
42 |
-
```
|
43 |
-
|
44 |
-
2. Install the dependencies:
|
45 |
-
|
46 |
-
```bash
|
47 |
-
pnpm install
|
48 |
-
```
|
49 |
-
|
50 |
-
3. Create a `.env` file in the `app` directory and add variables from [env.ts](https://github.com/IFRCGo/go-web-app/blob/develop/app/env.ts). Any variables marked with `.optional()` are not mandatory for setup and can be skipped.
|
51 |
-
|
52 |
-
```bash
|
53 |
-
cd app
|
54 |
-
touch .env
|
55 |
-
```
|
56 |
-
|
57 |
-
Example `.env` file
|
58 |
-
```
|
59 |
-
APP_TITLE=IFRC GO
|
60 |
-
APP_ENVIRONMENT=testing
|
61 |
-
...
|
62 |
-
```
|
63 |
-
|
64 |
-
4. Start the development server:
|
65 |
-
|
66 |
-
```bash
|
67 |
-
pnpm start:app
|
68 |
-
```
|
69 |
-
|
70 |
-
## Contributing
|
71 |
-
|
72 |
-
* Check out existing [Issues](https://github.com/IFRCGo/go-web-app/issues) and [Pull Requests](https://github.com/IFRCGo/go-web-app/pulls) to contribute.
|
73 |
-
* To request a feature or report a bug, [create a GitHub Issue](https://github.com/IFRCGo/go-web-app/issues/new/choose).
|
74 |
-
* [Contribution Guide →](./CONTRIBUTING.md)
|
75 |
-
* [Collaboration Guide →](./COLLABORATING.md)
|
76 |
-
|
77 |
-
## Additional Packages
|
78 |
-
|
79 |
-
The repository hosts multiple packages under the `packages` directory.
|
80 |
-
|
81 |
-
1. [IFRC GO UI](https://github.com/IFRCGo/go-web-app/tree/develop/packages/ui) is a React UI components library tailored to meet the specific requirements of the IFRC GO community and its associated projects.
|
82 |
-
2. [IFRC GO UI Storybook](https://github.com/IFRCGo/go-web-app/tree/develop/packages/go-ui-storybook) serves as the comprehensive showcase for the IFRC GO UI components library. It is hosted on [Chromatic](https://66557be6b68dacbf0a96db23-zctxglhsnk.chromatic.com/).
|
83 |
-
|
84 |
-
## IFRC GO Backend
|
85 |
-
|
86 |
-
The backend that serves the frontend application is maintained in a separate [repository](https://github.com/IFRCGo/go-api/).
|
87 |
-
|
88 |
-
## Previous Repository
|
89 |
-
|
90 |
-
[Go Frontend](https://github.com/IFRCGo/go-frontend) is the previous version of the project which contains the original codebase and project history.
|
91 |
-
|
92 |
-
## Community & Support
|
93 |
-
|
94 |
-
* Visit the [IFRC GO Wiki](https://go-wiki.ifrc.org/) for documentation of the IFRC GO platform.
|
95 |
-
* Stay informed about the latest project updates on [Medium](https://ifrcgoproject.medium.com/).
|
96 |
-
|
97 |
-
## License
|
98 |
-
|
99 |
-
[MIT](https://github.com/IFRCGo/go-web-app/blob/develop/LICENSE)
|
100 |
-
|
101 |
-
<!-- MARKDOWN LINKS & IMAGES -->
|
102 |
-
|
103 |
-
[react-shields]: https://img.shields.io/badge/react-%2320232a.svg?style=for-the-badge&logo=react&logoColor=%2361DAFB
|
104 |
-
|
105 |
-
[react-url]: https://reactjs.org/
|
106 |
-
|
107 |
-
[vite-shields]: https://img.shields.io/badge/vite-%23646CFF.svg?style=for-the-badge&logo=vite&logoColor=white
|
108 |
-
|
109 |
-
[vite-url]: https://vitejs.dev/
|
110 |
-
|
111 |
-
[typescript-shields]: https://img.shields.io/badge/typescript-%23007ACC.svg?style=for-the-badge&logo=typescript&logoColor=white
|
112 |
-
|
113 |
-
[typescript-url]: https://www.typescriptlang.org/
|
114 |
-
|
115 |
-
[pnpm-shields]: https://img.shields.io/badge/pnpm-F69220?style=for-the-badge&logo=pnpm&logoColor=fff
|
116 |
-
|
117 |
-
[pnpm-url]: https://pnpm.io/
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
go-web-app-develop/app/CHANGELOG.md
DELETED
@@ -1,729 +0,0 @@
|
|
1 |
-
# go-web-app
|
2 |
-
|
3 |
-
## 7.21.0-beta.2
|
4 |
-
|
5 |
-
### Patch Changes
|
6 |
-
|
7 |
-
- b949fcd: Fix use of operational timeframe date in imminent final report form
|
8 |
-
|
9 |
-
## 7.21.0-beta.1
|
10 |
-
|
11 |
-
### Patch Changes
|
12 |
-
|
13 |
-
- 84b4802: - Fix calculation of Operation End date in Final report form
|
14 |
-
- Fix icon position issue in the implementation table of DREF PDF export
|
15 |
-
- Update the label for last update date in the crisis categorization pop-up
|
16 |
-
|
17 |
-
## 7.21.0-beta.0
|
18 |
-
|
19 |
-
### Minor Changes
|
20 |
-
|
21 |
-
- 039c488: Add Crisis categorization update date
|
22 |
-
|
23 |
-
- Add updated date for crisis categorization in emergency page.
|
24 |
-
- Add consent checkbox over situational overview in field report form.
|
25 |
-
|
26 |
-
- 3ee9979: Add support for DREF imminent v2 in final report
|
27 |
-
|
28 |
-
- Add a separate route for the old dref final report form
|
29 |
-
- Update dref final report to accomodate imminent v2 changes
|
30 |
-
|
31 |
-
## 7.20.2
|
32 |
-
|
33 |
-
### Patch Changes
|
34 |
-
|
35 |
-
- 8090b9a: Fix other action section visibility condition in DREF export
|
36 |
-
|
37 |
-
## 7.20.1
|
38 |
-
|
39 |
-
### Patch Changes
|
40 |
-
|
41 |
-
- 4418171: Fix DREF form to properly save major coordination mechanism [#1928](https://github.com/IFRCGo/go-web-app/issues/1928)
|
42 |
-
|
43 |
-
## 7.20.1-beta.0
|
44 |
-
|
45 |
-
### Patch Changes
|
46 |
-
|
47 |
-
- 4418171: Fix DREF form to properly save major coordination mechanism [#1928](https://github.com/IFRCGo/go-web-app/issues/1928)
|
48 |
-
|
49 |
-
## 7.20.0
|
50 |
-
|
51 |
-
### Minor Changes
|
52 |
-
|
53 |
-
- 5771a6b: Update DREF application form and export
|
54 |
-
|
55 |
-
- add new field hazard date and location
|
56 |
-
- update hazard date as forcasted day of event
|
57 |
-
- update the section in dref application export
|
58 |
-
- remove Current National Society Actions from the export
|
59 |
-
|
60 |
-
## 7.20.0-beta.0
|
61 |
-
|
62 |
-
### Minor Changes
|
63 |
-
|
64 |
-
- 5771a6b: Update DREF application form and export
|
65 |
-
|
66 |
-
- add new field hazard date and location
|
67 |
-
- update hazard date as forcasted day of event
|
68 |
-
- update the section in dref application export
|
69 |
-
- remove Current National Society Actions from the export
|
70 |
-
|
71 |
-
## 7.19.0
|
72 |
-
|
73 |
-
### Minor Changes
|
74 |
-
|
75 |
-
- 456a145: Fix versioning
|
76 |
-
|
77 |
-
### Patch Changes
|
78 |
-
|
79 |
-
- 47786f8: Fix the undefined society name issue in surge page [#1899](https://github.com/IFRCGo/go-web-app/issues/1899)
|
80 |
-
|
81 |
-
## 7.18.2
|
82 |
-
|
83 |
-
### Patch Changes
|
84 |
-
|
85 |
-
- e51a80f: Update the action for the DREF Ops update form for imminent.
|
86 |
-
- Remove change to response modal in the ops update form for type imminent.
|
87 |
-
- Fix the order of the field in operational timeframe tab.
|
88 |
-
- Add description text under upload assessment report button in DREF operation update form
|
89 |
-
- Fix the error while viewing PER process [#1838](https://github.com/IFRCGo/go-web-app/issues/1838).
|
90 |
-
|
91 |
-
## 7.18.1
|
92 |
-
|
93 |
-
### Patch Changes
|
94 |
-
|
95 |
-
- 75bf525: Fix logic to disable ops update for old imminents
|
96 |
-
|
97 |
-
## 7.18.0
|
98 |
-
|
99 |
-
### Minor Changes
|
100 |
-
|
101 |
-
- bfcaecf: Address [Dref imminent Application](https://github.com/IFRCGo/go-web-app/issues/1455)
|
102 |
-
- Update logic for creation of dref final report for imminent
|
103 |
-
- Update allocatioon form for dref imminent
|
104 |
-
- Add Activity input in proposed action for dref type imminent
|
105 |
-
- Add proposed actions icons
|
106 |
-
- Show proposed actions for existing imminent dref applications
|
107 |
-
- Hide unused sections for dref imminent export and preserve proposed actions order
|
108 |
-
- Prevent selection of past dates for the `hazard_date` in dref imminent
|
109 |
-
- Add auto total population calculation in dref
|
110 |
-
- Add a confirmation popup before creating ops. update from imminent dref
|
111 |
-
|
112 |
-
### Patch Changes
|
113 |
-
|
114 |
-
- ee1bd60: Add proper redirect for Non-sovereign country in the country ongoing emergencies page
|
115 |
-
- 771d085: Community Based Surveillance updates (Surge CoS Health)
|
116 |
-
- Changed page: https://go.ifrc.org/surge/catalogue/health/community-based-surveillance
|
117 |
-
- The changes affect team size and some standard components (e.g. kit content)
|
118 |
-
- Updated dependencies [bfcaecf]
|
119 |
-
- @ifrc-go/[email protected]
|
120 |
-
|
121 |
-
## 7.17.4
|
122 |
-
|
123 |
-
### Patch Changes
|
124 |
-
|
125 |
-
- 14a7f2c: Update People assisted field label in the export of Dref final report.
|
126 |
-
|
127 |
-
## 7.17.3
|
128 |
-
|
129 |
-
### Patch Changes
|
130 |
-
|
131 |
-
- fc8b427: Update field label in DrefFinalReport form and export
|
132 |
-
|
133 |
-
## 7.17.2
|
134 |
-
|
135 |
-
### Patch Changes
|
136 |
-
|
137 |
-
- 54df6ff: Update DREF final report form
|
138 |
-
|
139 |
-
- The DREF final report form and export now include a new "Assisted Population" field, replacing the "Targeted Population" field.
|
140 |
-
|
141 |
-
## 7.17.1
|
142 |
-
|
143 |
-
### Patch Changes
|
144 |
-
|
145 |
-
- 215030a: Update DREF forms
|
146 |
-
|
147 |
-
- Move Response strategy description from placeholder to below the input
|
148 |
-
- Add DREF allocation field in event details for the Loan type Ops. update form
|
149 |
-
|
150 |
-
## 7.17.0
|
151 |
-
|
152 |
-
### Minor Changes
|
153 |
-
|
154 |
-
- 0b351d1: Address [DREF Superticket 2 bugs](https://github.com/IFRCGo/go-web-app/issues/1784)
|
155 |
-
|
156 |
-
- Update no of images in for "Description of event" from 2 to 4
|
157 |
-
- Update descriptions of few fields
|
158 |
-
- Replace \* with bullet in description of planned interventions in DREF import
|
159 |
-
- Add some of the missing fields to exports
|
160 |
-
- Remove warnings for previously removed fields
|
161 |
-
|
162 |
-
## 7.16.2
|
163 |
-
|
164 |
-
### Patch Changes
|
165 |
-
|
166 |
-
- c086629: Update Learn > Resources > Montandon page
|
167 |
-
- Update styling of 'API Access' buttons
|
168 |
-
- Reword 'Access API' link to 'Access Montandon API'
|
169 |
-
- Reword 'Explore Radiant Earth API' to 'Explore data in STAC browser'
|
170 |
-
- 2ee6a1e: Remove a broken image from Catalogue of Surge Services > Health > ERU Hospital page
|
171 |
-
|
172 |
-
## 7.16.1
|
173 |
-
|
174 |
-
### Patch Changes
|
175 |
-
|
176 |
-
- d561dc4: - Update Montandon landing page - Fix typo in Justin's name and email - Update description
|
177 |
-
- Fix position and deploying organisation in ongoing RR deployment table
|
178 |
-
|
179 |
-
## 7.16.0
|
180 |
-
|
181 |
-
### Minor Changes
|
182 |
-
|
183 |
-
- 9dcdd38: Add Montandon landing page
|
184 |
-
|
185 |
-
- Add a basic landing page for Montandon with links and information
|
186 |
-
- Add link to Montandon landing page to Learn > Resources menu
|
187 |
-
|
188 |
-
## 7.15.0
|
189 |
-
|
190 |
-
### Minor Changes
|
191 |
-
|
192 |
-
- c26bda4: Implement [ERU Readiness](https://github.com/IFRCGo/go-web-app/issues/1710)
|
193 |
-
|
194 |
-
- Restucture surge page to acommodate ERU
|
195 |
-
- Move surge deployment related sections to a new dedicated tab **Active Surge Deployments**
|
196 |
-
- Update active deployments to improve scaling of points in the map
|
197 |
-
- Add **Active Surge Support per Emergency** section
|
198 |
-
- Revamp **Surge Overview** tab
|
199 |
-
- Add **Rapid Response Personnel** sub-tab
|
200 |
-
- Update existings charts and add new related tables/charts
|
201 |
-
- Add **Emergency Response Unit** sub-tab
|
202 |
-
- Add section to visualize ERU capacity and readiness
|
203 |
-
- Add section to view ongoing ERU deployments
|
204 |
-
- Add a form to update ERU Readiness
|
205 |
-
- Add option to export ERU Readiness data
|
206 |
-
- Update **Respond > Surge/Deployments** menu to include **Active Surge Deployments**
|
207 |
-
|
208 |
-
- 9ed8181: Address feedbacks in [DREF superticket feedbacks](https://github.com/IFRCGo/go-web-app/issues/1816)
|
209 |
-
|
210 |
-
- Make end date of operation readonly field in all DREF forms
|
211 |
-
- Fix font and spacing issues in the DREF exports (caused by link text overflow)
|
212 |
-
- Update styling of Risk and Security Considerations section to match that of Previous Operations
|
213 |
-
- Update visibility condition of National Society Actions in Final Report export
|
214 |
-
|
215 |
-
### Patch Changes
|
216 |
-
|
217 |
-
- Updated dependencies [c26bda4]
|
218 |
-
- @ifrc-go/[email protected]
|
219 |
-
|
220 |
-
## 7.14.0
|
221 |
-
|
222 |
-
### Minor Changes
|
223 |
-
|
224 |
-
- 18ccc85:
|
225 |
-
- Update styling of vertical NavigationTab
|
226 |
-
- Hide register URL in the T&C page for logged in user
|
227 |
-
- Update styling of T&C page
|
228 |
-
- Make the page responsive
|
229 |
-
- Make sidebar sticky
|
230 |
-
- Update url for [monty docs](https://github.com/IFRCGo/go-web-app/issues/1418#issuecomment-2422371363)
|
231 |
-
- 8d3a7bd: Initiate shutdown for 3W
|
232 |
-
- Remove "Submit 3W Projects" from the menu Prepare > Global 3W projects
|
233 |
-
- Rename "Global 3W Projects" to "Programmatic Partnerships" in Prepare menu
|
234 |
-
- Update global 3W page
|
235 |
-
- Update title and description for Programmatic Partnerships
|
236 |
-
- Remove all the contents related to 3W
|
237 |
-
- Replace contents in various places with project shutdown message
|
238 |
-
- Regional 3W tab
|
239 |
-
- 3W Projects section in Accounts > My Form > 3W
|
240 |
-
- Projects tab in Country > Ongoing Activities
|
241 |
-
- All Projects page
|
242 |
-
- New, edit 3W project form
|
243 |
-
- View 3W project page
|
244 |
-
- Remove NS Activities section in Country > NS overview > NS Activities page
|
245 |
-
- Remove Projects section from search results page
|
246 |
-
|
247 |
-
### Patch Changes
|
248 |
-
|
249 |
-
- Updated dependencies [18ccc85]
|
250 |
-
- @ifrc-go/[email protected]
|
251 |
-
|
252 |
-
## 7.13.0
|
253 |
-
|
254 |
-
### Minor Changes
|
255 |
-
|
256 |
-
- 69fd74f: - Update page title for Emergency to include the name
|
257 |
-
- Update page title of Flash update to include the name
|
258 |
-
- Fix the user registration link in the Terms & Condition page
|
259 |
-
- 680c673: Implement [DREF Superticket 2.0](https://github.com/IFRCGo/go-web-app/issues/1695)
|
260 |
-
|
261 |
-
### Patch Changes
|
262 |
-
|
263 |
-
- fe4b727: - Upgrade pnpm to v10.6.1
|
264 |
-
- Cleanup Dockerfile
|
265 |
-
- Configure depandabot to track other dependencies updates
|
266 |
-
- Upgrade eslint
|
267 |
-
- Use workspace protocol to reference workspace packages
|
268 |
-
- 9f20016: Enable user to edit their position field in [#1647](https://github.com/IFRCGo/go-web-app/issues/1647)
|
269 |
-
- ef15af1: Add secondary ordering in tables for rows with same date
|
270 |
-
- Updated dependencies [fe4b727]
|
271 |
-
- @ifrc-go/[email protected]
|
272 |
-
|
273 |
-
## 7.12.1
|
274 |
-
|
275 |
-
### Patch Changes
|
276 |
-
|
277 |
-
- Fix nullable type of assessment for NS capacity
|
278 |
-
|
279 |
-
## 7.12.0
|
280 |
-
|
281 |
-
### Minor Changes
|
282 |
-
|
283 |
-
- f766bc7: Add link to IFRC Survey Designer in the tools section under learn menu
|
284 |
-
|
285 |
-
### Patch Changes
|
286 |
-
|
287 |
-
- 7f51854: - Surge CoS: Health fix
|
288 |
-
- 3a1cac8: Hide focal point details based on user permissions
|
289 |
-
- 43d3bf1: - Add Surge CoS Administration section
|
290 |
-
- Add Surge CoS Faecal Sludge Management (FSM) section
|
291 |
-
- Update Surge CoS IT&T section
|
292 |
-
- Update Surge CoS Basecamp section (as OSH)
|
293 |
-
|
294 |
-
## 7.11.1
|
295 |
-
|
296 |
-
### Patch Changes
|
297 |
-
|
298 |
-
- ff426cd: Use current language for field report title generation
|
299 |
-
|
300 |
-
## 7.11.0
|
301 |
-
|
302 |
-
### Minor Changes
|
303 |
-
|
304 |
-
- Field report number generation: Change only when the country or event changes
|
305 |
-
|
306 |
-
## 7.10.1
|
307 |
-
|
308 |
-
### Patch Changes
|
309 |
-
|
310 |
-
- 14567f1: Improved tables by adding default and second-level ordering in [#1633](https://github.com/IFRCGo/go-web-app/issues/1633)
|
311 |
-
|
312 |
-
- Appeal Documents table, `emergencies/{xxx}/reports` page
|
313 |
-
- Recent Emergencies in Regions – All Appeals table
|
314 |
-
- All Deployed Personnel – Default sorting (filters to be added)
|
315 |
-
- Deployed ERUs – Changed filter title
|
316 |
-
- Key Documents tables in Countries
|
317 |
-
- Response documents
|
318 |
-
- Main page – Active Operations table
|
319 |
-
- The same `AppealsTable` is used in:
|
320 |
-
- Active Operations in Regions
|
321 |
-
- Previous Operations in Countries
|
322 |
-
|
323 |
-
- 78d25b2:
|
324 |
-
|
325 |
-
- Update on the ERU MHPSS Module in the Catalogue of Services in [#1648](https://github.com/IFRCGo/go-web-app/issues/1648)
|
326 |
-
- Update on a PER role profile in [#1648](https://github.com/IFRCGo/go-web-app/issues/1648)
|
327 |
-
- Update link to the IM Technical Competency Framework in [#1483](https://github.com/IFRCGo/go-web-app/issues/1483)
|
328 |
-
|
329 |
-
- 44623a7: Undo DREF Imminent changes
|
330 |
-
- b57c453: Show the number of people assisted in the DREF Final Report export in [#1665](https://github.com/IFRCGo/go-web-app/issues/1665)
|
331 |
-
|
332 |
-
## 7.10.0
|
333 |
-
|
334 |
-
### Minor Changes
|
335 |
-
|
336 |
-
- 4f89133: Fix DREF PGA export styling
|
337 |
-
|
338 |
-
## 7.9.0
|
339 |
-
|
340 |
-
### Minor Changes
|
341 |
-
|
342 |
-
- 7927522: Update Imminent DREF Application in [#1455](https://github.com/IFRCGo/go-web-app/issues/1455)
|
343 |
-
|
344 |
-
- Hide sections/fields
|
345 |
-
- Rename sections/fields
|
346 |
-
- Remove sections/fields
|
347 |
-
- Reflect changes in the PDF export
|
348 |
-
|
349 |
-
### Patch Changes
|
350 |
-
|
351 |
-
- Updated dependencies [4032688]
|
352 |
-
- @ifrc-go/[email protected]
|
353 |
-
|
354 |
-
## 7.8.1
|
355 |
-
|
356 |
-
### Patch Changes
|
357 |
-
|
358 |
-
- 9c51dee: Remove `summary` field from field report form
|
359 |
-
- Update @ifrc-go/ui version
|
360 |
-
|
361 |
-
## 7.8.0
|
362 |
-
|
363 |
-
### Minor Changes
|
364 |
-
|
365 |
-
- 4843cb0: Added Operational Learning 2.0
|
366 |
-
|
367 |
-
- Key Figures Overview in Operational Learning
|
368 |
-
- Map View for Operational Learning
|
369 |
-
- Learning by Sector Bar Chart
|
370 |
-
- Learning by Region Bar Chart
|
371 |
-
- Sources Over Time Line Chart
|
372 |
-
- Methodology changes for the prioritization step
|
373 |
-
- Added an option to regenerate cached summaries
|
374 |
-
- Summary post-processing and cleanup
|
375 |
-
- Enabled MDR code search in admin
|
376 |
-
|
377 |
-
### Patch Changes
|
378 |
-
|
379 |
-
- f96e177: Move field report/emergency title generation logic from client to server
|
380 |
-
- e85fc32: Integrate `crate-ci/typos` for code spell checking
|
381 |
-
- 4cdea2b: Add redirection logic for `preparedness#operational-learning`
|
382 |
-
- 9a50443: Add appeal doc type for appeal documents
|
383 |
-
- 817d56d: Display properly formatted appeal type in search results
|
384 |
-
- 1159fa4: Redirect obsolete URLs to recent ones
|
385 |
-
- redirect `/reports/` to `/field-reports/`
|
386 |
-
- redirect `/deployments/` -> `/surge/overview`
|
387 |
-
- Updated dependencies [4843cb0]
|
388 |
-
- @ifrc-go/[email protected]
|
389 |
-
|
390 |
-
## 7.7.0
|
391 |
-
|
392 |
-
### Minor Changes
|
393 |
-
|
394 |
-
- 3258b96: Add local unit validation workflow
|
395 |
-
|
396 |
-
### Patch Changes
|
397 |
-
|
398 |
-
- Updated dependencies [c5a446f]
|
399 |
-
- @ifrc-go/[email protected]
|
400 |
-
|
401 |
-
## 7.6.6
|
402 |
-
|
403 |
-
### Patch Changes
|
404 |
-
|
405 |
-
- 8cdc946: Hide Local unit contact details on the list view for logged in users in [#1485](https://github.com/ifRCGo/go-web-app/issues/1485)
|
406 |
-
Update `tinymce-react` plugin to the latest version and enabled additional plugins, including support for lists in [#1481](https://github.com/ifRCGo/go-web-app/issues/1481)
|
407 |
-
- ecca810: Replace the from-communication-copied text of CoS Health header
|
408 |
-
- 7cf2514: Prioritize GDACS as the Primary Source for Imminent Risk Watch in [#1547](https://github.com/IFRCGo/go-web-app/issues/1547)
|
409 |
-
- 8485076: Add Organization type and Learning type filter in Operational learning in [#1469](https://github.com/IFRCGo/go-web-app/issues/1469)
|
410 |
-
- 766d98d: Auto append https:// for incomplete URLs in [#1505](https://github.com/IFRCGo/go-web-app/issues/1505)
|
411 |
-
|
412 |
-
## 7.6.5
|
413 |
-
|
414 |
-
### Patch Changes
|
415 |
-
|
416 |
-
- 478e73b: Update labels for severity control in Imminent Risk Map
|
417 |
-
Update navigation for the events in Imminent Risk Map
|
418 |
-
Fix issue displayed when opening a DREF import template
|
419 |
-
Fix submission issue when importing a DREF import file
|
420 |
-
- f82f846: Update Health Section in Catalogue of Surge Services
|
421 |
-
- ade84aa: Display ICRC Presence
|
422 |
-
- Display ICRC presence across partner countries
|
423 |
-
- Highlight key operational countries
|
424 |
-
|
425 |
-
## 7.6.4
|
426 |
-
|
427 |
-
### Patch Changes
|
428 |
-
|
429 |
-
- d85f64d: Update Imminent Events
|
430 |
-
|
431 |
-
- Hide WFP ADAM temporarily from list sources
|
432 |
-
- Show exposure control for cyclones from GDACS only
|
433 |
-
|
434 |
-
## 7.6.3
|
435 |
-
|
436 |
-
### Patch Changes
|
437 |
-
|
438 |
-
- 7bbf3d2: Update key insights disclaimer text in Ops. Learning
|
439 |
-
- 0e40681: Update FDRS data in Country / Context and Structure / NS indicators
|
440 |
-
|
441 |
-
- Add separate icon for each field for data year
|
442 |
-
- Use separate icon for disaggregation
|
443 |
-
- Update descriptions on dref import template (more details on _Missing / to be implemented_ section in https://github.com/IFRCGo/go-web-app/pull/1434#issuecomment-2459034932)
|
444 |
-
|
445 |
-
- Updated dependencies [801ec3c]
|
446 |
-
- @ifrc-go/[email protected]
|
447 |
-
|
448 |
-
## 7.6.2
|
449 |
-
|
450 |
-
### Patch Changes
|
451 |
-
|
452 |
-
- 4fa6a36: Updated PER terminology and add PER logo in PER PDF export
|
453 |
-
- 813e93f: Add link to GO UI storybook in resources page
|
454 |
-
- 20dfeb3: Update DREF import template
|
455 |
-
- Update guidance
|
456 |
-
- Improve template stylings
|
457 |
-
- Update message in error popup when import fails
|
458 |
-
- 8a18ad8: Add beta tag, URL redirect, and link to old dashboard on Ops Learning
|
459 |
-
|
460 |
-
## 7.6.1
|
461 |
-
|
462 |
-
### Patch Changes
|
463 |
-
|
464 |
-
- 7afaf34: Fix null event in appeal for operational learning
|
465 |
-
|
466 |
-
## 7.6.0
|
467 |
-
|
468 |
-
### Minor Changes
|
469 |
-
|
470 |
-
- Add new Operational Learning Page
|
471 |
-
|
472 |
-
- Add link to Operational Learning page under `Learn` navigation menu
|
473 |
-
- Integrate LLM summaries for Operational Learning
|
474 |
-
|
475 |
-
## 7.5.3
|
476 |
-
|
477 |
-
### Patch Changes
|
478 |
-
|
479 |
-
- d7f5f53: Revamp risk imminent events for cyclone
|
480 |
-
- Visualize storm position, forecast uncertainty, track line and exposed area differently
|
481 |
-
- Add option to toggle visibility of these different layers
|
482 |
-
- Add severity legend for exposure
|
483 |
-
- Update styling for items in event list
|
484 |
-
- Update styling for event details page
|
485 |
-
- 36a64fa: Integrate multi-select functionality in operational learning filters to allow selection of multiple filter items.
|
486 |
-
- 894d00c: Add a new 404 page
|
487 |
-
- 7757e54: Add an option to download excel import template for DREF (Response) which user can fill up and import.
|
488 |
-
- a8d021d: Update resources page
|
489 |
-
- Add a new video for LocalUnits
|
490 |
-
- Update ordering of videos
|
491 |
-
- aea512d: Prevent users from pasting images into rich text field
|
492 |
-
- fd54657: Add Terms and Conditions page
|
493 |
-
- bf55ccc: Add Cookie Policy page
|
494 |
-
- df80c4f: Fix contact details in Field Report being always required when filled once
|
495 |
-
- 81dc3bd: Added color mapping based on PER Area and Rating across all PER charts
|
496 |
-
- Updated dependencies [dd92691]
|
497 |
-
- Updated dependencies [d7f5f53]
|
498 |
-
- Updated dependencies [fe6a455]
|
499 |
-
- Updated dependencies [81dc3bd]
|
500 |
-
- @ifrc-go/[email protected]
|
501 |
-
|
502 |
-
## 7.5.2
|
503 |
-
|
504 |
-
### Patch Changes
|
505 |
-
|
506 |
-
- 37bba31: Add collaboration guide
|
507 |
-
|
508 |
-
## 7.5.1
|
509 |
-
|
510 |
-
### Patch Changes
|
511 |
-
|
512 |
-
- 2a5e4a1: Add Core Competency Framework link to Resources page in [#1331](https://github.com/IFRCGo/go-web-app/issues/1331)
|
513 |
-
- 31eaa97: Add Health Mapping Report to Resources page in [#1331](https://github.com/IFRCGo/go-web-app/issues/1331)
|
514 |
-
- 4192da1: - Local Units popup, view/edit mode improvements in [#1178](https://github.com/IFRCGo/go-web-app/issues/1178)
|
515 |
-
- Remove ellipsize heading option in local units map popup
|
516 |
-
- Local units title on popup are now clickable that opens up a modal to show details
|
517 |
-
- Added an Edit button to the View Mode for users with edit permissions
|
518 |
-
- Users will now see a **disabled grey button** when the content is already validated
|
519 |
-
- 5c7ab88: Display the public visibility field report to public users in [#1743](https://github.com/IFRCGo/go-web-app/issues/1343)
|
520 |
-
|
521 |
-
## 7.5.0
|
522 |
-
|
523 |
-
### Minor Changes
|
524 |
-
|
525 |
-
- 5845699: Clean up Resources page
|
526 |
-
|
527 |
-
## 7.4.2
|
528 |
-
|
529 |
-
### Patch Changes
|
530 |
-
|
531 |
-
- d734e04: - Fix duplication volunteer label in the Field Report details
|
532 |
-
- Fix rating visibility in the Country > NS Overview > Strategic priorities page
|
533 |
-
|
534 |
-
## 7.4.1
|
535 |
-
|
536 |
-
### Patch Changes
|
537 |
-
|
538 |
-
- a4f77ab: Fetch and use latest available WorldBank data in [#571](https://github.com/IFRCGo/go-api/issues/2224)
|
539 |
-
- ebf033a: Update Technical Competencies Link on the Cash page of the Catalogue of Surge Services in [#1290](https://github.com/IFRCGo/go-web-app/issues/1290)
|
540 |
-
- 18d0dc9: Use `molnix status` to filter surge alerts in [#2208](https://github.com/IFRCGo/go-api/issues/2208)
|
541 |
-
- b070c66: Check guest user permission for local units
|
542 |
-
- 72df1f2: Add new drone icon for UAV team in [#1280](https://github.com/IFRCGo/go-web-app/issues/1280)
|
543 |
-
- 2ff7940: Link version number to release notes on GitHub in [#1004](https://github.com/IFRCGo/go-web-app/issues/1004)
|
544 |
-
Updated @ifrc-go/icons to v2.0.1
|
545 |
-
- Updated dependencies [72df1f2]
|
546 |
-
- @ifrc-go/[email protected]
|
547 |
-
|
548 |
-
## 7.4.0
|
549 |
-
|
550 |
-
### Minor Changes
|
551 |
-
|
552 |
-
- b6bd6aa: Implement Guest User Permission in [#1237](https://github.com/IFRCGo/go-web-app/issues/1237)
|
553 |
-
|
554 |
-
## 7.3.13
|
555 |
-
|
556 |
-
### Patch Changes
|
557 |
-
|
558 |
-
- 453a397: - Update Local Unit map, table and form to match the updated design in [#1178](https://github.com/IFRCGo/go-web-app/issues/1178)
|
559 |
-
- Add delete button in Local units table and form
|
560 |
-
- Use filter prop in container and remove manual stylings
|
561 |
-
- Update size of WikiLink to match height of other action items
|
562 |
-
- Add error boundary to BaseMap component
|
563 |
-
- Updated dependencies [453a397]
|
564 |
-
- @ifrc-go/[email protected]
|
565 |
-
|
566 |
-
## 7.3.12
|
567 |
-
|
568 |
-
### Patch Changes
|
569 |
-
|
570 |
-
- ba6734e: Show admin labels in maps in different languages, potentially fixing [#1036](https://github.com/IFRCGo/go-web-app/issues/1036)
|
571 |
-
|
572 |
-
## 7.3.11
|
573 |
-
|
574 |
-
### Patch Changes
|
575 |
-
|
576 |
-
- d9491a2: Fix appeals statistics calculation
|
577 |
-
|
578 |
-
## 7.3.10
|
579 |
-
|
580 |
-
### Patch Changes
|
581 |
-
|
582 |
-
- 3508c83: Add missing validations in DREF forms
|
583 |
-
- 3508c83: Fix region filter in All Appeals table
|
584 |
-
- 073fa1e: Remove personal detail for focal point in local units table
|
585 |
-
- b508475: Add June 2024 Catalogue of Surge Services Updates
|
586 |
-
- 3508c83: Handle countries with no bounding box
|
587 |
-
- d9491a2: Fix appeals based statistics calculation
|
588 |
-
- Updated dependencies [073fa1e]
|
589 |
-
- @ifrc-go/[email protected]
|
590 |
-
|
591 |
-
## 7.3.9
|
592 |
-
|
593 |
-
### Patch Changes
|
594 |
-
|
595 |
-
- 49f5410: - Reorder CoS list
|
596 |
-
- Update texts in CoS strategic partnerships resource mobilisation
|
597 |
-
|
598 |
-
## 7.3.8
|
599 |
-
|
600 |
-
### Patch Changes
|
601 |
-
|
602 |
-
- 478ab69: Hide contact information from IFRC Presence
|
603 |
-
- 3fbe60f: Hide add/edit local units on production environment
|
604 |
-
- 90678ed: Show Organization Type properly in Account Details page
|
605 |
-
|
606 |
-
## 7.3.7
|
607 |
-
|
608 |
-
### Patch Changes
|
609 |
-
|
610 |
-
- 909a5e2: Fix Appeals table for Africa Region
|
611 |
-
- 5a1ae43: Add presentation mode in local units map
|
612 |
-
- 96120aa: Fix DREF exports margins and use consistent date format
|
613 |
-
- 8a4f26d: Avoid crash on country pages for countries without bbox
|
614 |
-
|
615 |
-
## 7.3.6
|
616 |
-
|
617 |
-
### Patch Changes
|
618 |
-
|
619 |
-
- 1b4b6df: Add local unit form
|
620 |
-
- 2631a9f: Add office type and location information for IFRC delegation office
|
621 |
-
- 2d7a6a5: - Enable ability to start PER in IFRC supported languages
|
622 |
-
- Make PER forms `readOnly` in case of language mismatch
|
623 |
-
- e4bf098: Fix incorrect statistics for past appeals of a country
|
624 |
-
- Updated dependencies [0ab207d]
|
625 |
-
- Updated dependencies [66151a7]
|
626 |
-
- @ifrc-go/[email protected]
|
627 |
-
|
628 |
-
## 7.3.5
|
629 |
-
|
630 |
-
### Patch Changes
|
631 |
-
|
632 |
-
- 894a762: Fix seasonal risk score in regional and global risk watch
|
633 |
-
|
634 |
-
## 7.3.4
|
635 |
-
|
636 |
-
### Patch Changes
|
637 |
-
|
638 |
-
- d368ada: Fix GNI per capita in country profile overview
|
639 |
-
|
640 |
-
## 7.3.3
|
641 |
-
|
642 |
-
### Patch Changes
|
643 |
-
|
644 |
-
- 73e1966: Update CoS pages as mentioned in #913
|
645 |
-
- 179a073: Show all head of delegation under IFRC Presence
|
646 |
-
- 98d6b62: Fix region operation map to apply filter for Africa
|
647 |
-
|
648 |
-
## 7.3.2
|
649 |
-
|
650 |
-
### Patch Changes
|
651 |
-
|
652 |
-
- f83c12b: Show Local name when available and use English name as fallback for local units data
|
653 |
-
|
654 |
-
## 7.3.1
|
655 |
-
|
656 |
-
### Patch Changes
|
657 |
-
|
658 |
-
- 7f0212b: Integrate mapbox street view for local units map
|
659 |
-
- Updated dependencies [7f0212b]
|
660 |
-
- @ifrc-go/[email protected]
|
661 |
-
|
662 |
-
## 7.3.0
|
663 |
-
|
664 |
-
### Minor Changes
|
665 |
-
|
666 |
-
- 0dffd52: Add table view in NS local units
|
667 |
-
|
668 |
-
## 7.2.5
|
669 |
-
|
670 |
-
### Patch Changes
|
671 |
-
|
672 |
-
- 556766e: - Refetch token list after new token is created
|
673 |
-
- Update link for terms and conditions for Montandon
|
674 |
-
|
675 |
-
## 7.2.4
|
676 |
-
|
677 |
-
### Patch Changes
|
678 |
-
|
679 |
-
- 30eac3c: Add option to generate API token for Montandon in the user profile
|
680 |
-
|
681 |
-
## 7.2.3
|
682 |
-
|
683 |
-
### Patch Changes
|
684 |
-
|
685 |
-
- Fix crash due to undefined ICRC presence in country page
|
686 |
-
|
687 |
-
## 7.2.2
|
688 |
-
|
689 |
-
### Patch Changes
|
690 |
-
|
691 |
-
- - Update country risk page sources
|
692 |
-
- Update CoS pages
|
693 |
-
- Updated dependencies [a1c0554]
|
694 |
-
- Updated dependencies [e9552b4]
|
695 |
-
- @ifrc-go/[email protected]
|
696 |
-
|
697 |
-
## 7.2.1
|
698 |
-
|
699 |
-
### Patch Changes
|
700 |
-
|
701 |
-
- Remove personal identifiable information for local units
|
702 |
-
|
703 |
-
## 7.2.0
|
704 |
-
|
705 |
-
### Minor Changes
|
706 |
-
|
707 |
-
- 9657d4b: Update country pages with appropriate source links
|
708 |
-
- 66fa7cf: Show FDRS data retrieval year in NS indicators
|
709 |
-
- b69e8e5: Update IFRC legal status link
|
710 |
-
- 300250a: Show latest strategic plan of National Society under Strategic Priorities
|
711 |
-
- 9657d4b: Add GO Wiki links for country page sections
|
712 |
-
- b38d9d9: Improve overall styling of country pages
|
713 |
-
- Make loading animation consistent across all pages
|
714 |
-
- Make empty message consistent
|
715 |
-
- Use ChartContainer and update usage of charting hooks
|
716 |
-
- Update BaseMap to extend defaultMapOptions (instead of replacing it)
|
717 |
-
- Add an option to provide popupClassName in MapPopup
|
718 |
-
- 80be711: Rename `Supporting Partners` to `Partners`.
|
719 |
-
- Update IFRC legal status link.
|
720 |
-
- Update the name of the strategic priorities link to indicate that they were created by the National Society.
|
721 |
-
- 176e01b: Simplify usage of PER question group in PER assessment form
|
722 |
-
- Add min widths in account table columns
|
723 |
-
|
724 |
-
## 7.1.5
|
725 |
-
|
726 |
-
### Patch Changes
|
727 |
-
|
728 |
-
- Updated dependencies
|
729 |
-
- @ifrc-go/[email protected]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
go-web-app-develop/app/env.ts
DELETED
@@ -1,29 +0,0 @@
|
|
1 |
-
import { defineConfig, Schema } from '@julr/vite-plugin-validate-env';
|
2 |
-
|
3 |
-
export default defineConfig({
|
4 |
-
APP_TITLE: Schema.string(),
|
5 |
-
APP_ENVIRONMENT: (key, value) => {
|
6 |
-
// NOTE: APP_ENVIRONMENT_PLACEHOLDER is meant to be used with image builds
|
7 |
-
// The value will be later replaced with the actual value
|
8 |
-
const regex = /^production|staging|testing|alpha-\d+|development|APP_ENVIRONMENT_PLACEHOLDER$/;
|
9 |
-
const valid = !!value && (value.match(regex) !== null);
|
10 |
-
if (!valid) {
|
11 |
-
throw new Error(`Value for environment variable "${key}" must match regex "${regex}", instead received "${value}"`);
|
12 |
-
}
|
13 |
-
if (value === 'APP_ENVIRONMENT_PLACEHOLDER') {
|
14 |
-
console.warn(`Using ${value} for app environment. Make sure to not use this for builds without helm chart`)
|
15 |
-
}
|
16 |
-
return value as ('production' | 'staging' | 'testing' | `alpha-${number}` | 'development' | 'APP_ENVIRONMENT_PLACEHOLDER');
|
17 |
-
},
|
18 |
-
APP_API_ENDPOINT: Schema.string({ format: 'url', protocol: true, tld: false }),
|
19 |
-
APP_ADMIN_URL: Schema.string.optional({ format: 'url', protocol: true, tld: false }),
|
20 |
-
APP_MAPBOX_ACCESS_TOKEN: Schema.string(),
|
21 |
-
APP_TINY_API_KEY: Schema.string(),
|
22 |
-
APP_RISK_API_ENDPOINT: Schema.string({ format: 'url', protocol: true }),
|
23 |
-
APP_SDT_URL: Schema.string.optional({ format: 'url', protocol: true, tld: false }),
|
24 |
-
APP_SENTRY_DSN: Schema.string.optional(),
|
25 |
-
APP_SENTRY_TRACES_SAMPLE_RATE: Schema.number.optional(),
|
26 |
-
APP_SENTRY_REPLAYS_SESSION_SAMPLE_RATE: Schema.number.optional(),
|
27 |
-
APP_SENTRY_REPLAYS_ON_ERROR_SAMPLE_RATE: Schema.number.optional(),
|
28 |
-
APP_GOOGLE_ANALYTICS_ID: Schema.string.optional(),
|
29 |
-
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
go-web-app-develop/app/eslint.config.js
DELETED
@@ -1,165 +0,0 @@
|
|
1 |
-
import { FlatCompat } from '@eslint/eslintrc';
|
2 |
-
import js from '@eslint/js';
|
3 |
-
import json from "@eslint/json";
|
4 |
-
import tseslint from "typescript-eslint";
|
5 |
-
import process from 'process';
|
6 |
-
|
7 |
-
const dirname = process.cwd();
|
8 |
-
|
9 |
-
const compat = new FlatCompat({
|
10 |
-
baseDirectory: dirname,
|
11 |
-
resolvePluginsRelativeTo: dirname,
|
12 |
-
});
|
13 |
-
|
14 |
-
const appConfigs = compat.config({
|
15 |
-
env: {
|
16 |
-
node: true,
|
17 |
-
browser: true,
|
18 |
-
es2020: true,
|
19 |
-
},
|
20 |
-
root: true,
|
21 |
-
extends: [
|
22 |
-
'airbnb',
|
23 |
-
'airbnb/hooks',
|
24 |
-
'plugin:@typescript-eslint/recommended',
|
25 |
-
'plugin:react-hooks/recommended',
|
26 |
-
],
|
27 |
-
parser: '@typescript-eslint/parser',
|
28 |
-
parserOptions: {
|
29 |
-
ecmaVersion: 'latest',
|
30 |
-
sourceType: 'module',
|
31 |
-
},
|
32 |
-
plugins: [
|
33 |
-
'@typescript-eslint',
|
34 |
-
'react-refresh',
|
35 |
-
'simple-import-sort',
|
36 |
-
'import-newlines'
|
37 |
-
],
|
38 |
-
settings: {
|
39 |
-
'import/parsers': {
|
40 |
-
'@typescript-eslint/parser': ['.ts', '.tsx']
|
41 |
-
},
|
42 |
-
'import/resolver': {
|
43 |
-
typescript: {
|
44 |
-
project: [
|
45 |
-
'./tsconfig.json',
|
46 |
-
],
|
47 |
-
},
|
48 |
-
},
|
49 |
-
},
|
50 |
-
rules: {
|
51 |
-
'react-refresh/only-export-components': 'warn',
|
52 |
-
|
53 |
-
'no-unused-vars': 0,
|
54 |
-
'@typescript-eslint/no-unused-vars': 1,
|
55 |
-
|
56 |
-
'no-use-before-define': 0,
|
57 |
-
'@typescript-eslint/no-use-before-define': 1,
|
58 |
-
|
59 |
-
'no-shadow': 0,
|
60 |
-
'@typescript-eslint/no-shadow': ['error'],
|
61 |
-
|
62 |
-
'@typescript-eslint/consistent-type-imports': [
|
63 |
-
'warn',
|
64 |
-
{
|
65 |
-
disallowTypeAnnotations: false,
|
66 |
-
fixStyle: 'inline-type-imports',
|
67 |
-
prefer: 'type-imports',
|
68 |
-
},
|
69 |
-
],
|
70 |
-
|
71 |
-
'import/no-extraneous-dependencies': [
|
72 |
-
'error',
|
73 |
-
{
|
74 |
-
devDependencies: [
|
75 |
-
'**/*.test.{ts,tsx}',
|
76 |
-
'eslint.config.js',
|
77 |
-
'postcss.config.cjs',
|
78 |
-
'stylelint.config.cjs',
|
79 |
-
'vite.config.ts',
|
80 |
-
],
|
81 |
-
optionalDependencies: false,
|
82 |
-
},
|
83 |
-
],
|
84 |
-
|
85 |
-
indent: ['error', 4, { SwitchCase: 1 }],
|
86 |
-
|
87 |
-
'import/no-cycle': ['error', { allowUnsafeDynamicCyclicDependency: true }],
|
88 |
-
|
89 |
-
'react/react-in-jsx-scope': 'off',
|
90 |
-
'camelcase': 'off',
|
91 |
-
|
92 |
-
'react/jsx-indent': ['error', 4],
|
93 |
-
'react/jsx-indent-props': ['error', 4],
|
94 |
-
'react/jsx-filename-extension': ['error', { extensions: ['.js', '.jsx', '.ts', '.tsx'] }],
|
95 |
-
|
96 |
-
'import/extensions': ['off', 'never'],
|
97 |
-
|
98 |
-
'react-hooks/rules-of-hooks': 'error',
|
99 |
-
'react-hooks/exhaustive-deps': 'warn',
|
100 |
-
|
101 |
-
'react/require-default-props': ['warn', { ignoreFunctionalComponents: true }],
|
102 |
-
'simple-import-sort/imports': 'warn',
|
103 |
-
'simple-import-sort/exports': 'warn',
|
104 |
-
'import-newlines/enforce': ['warn', 1]
|
105 |
-
},
|
106 |
-
overrides: [
|
107 |
-
{
|
108 |
-
files: ['*.js', '*.jsx', '*.ts', '*.tsx'],
|
109 |
-
rules: {
|
110 |
-
'simple-import-sort/imports': [
|
111 |
-
'error',
|
112 |
-
{
|
113 |
-
'groups': [
|
114 |
-
// side effect imports
|
115 |
-
['^\\u0000'],
|
116 |
-
// packages `react` related packages come first
|
117 |
-
['^react', '^@?\\w'],
|
118 |
-
// internal packages
|
119 |
-
['^#.+$'],
|
120 |
-
// parent imports. Put `..` last
|
121 |
-
// other relative imports. Put same-folder imports and `.` last
|
122 |
-
['^\\.\\.(?!/?$)', '^\\.\\./?$', '^\\./(?=.*/)(?!/?$)', '^\\.(?!/?$)', '^\\./?$'],
|
123 |
-
// style imports
|
124 |
-
['^.+\\.json$', '^.+\\.module.css$'],
|
125 |
-
]
|
126 |
-
}
|
127 |
-
]
|
128 |
-
}
|
129 |
-
}
|
130 |
-
]
|
131 |
-
}).map((conf) => ({
|
132 |
-
...conf,
|
133 |
-
files: ['src/**/*.tsx', 'src/**/*.jsx', 'src/**/*.ts', 'src/**/*.js'],
|
134 |
-
ignores: [
|
135 |
-
"node_modules/",
|
136 |
-
"build/",
|
137 |
-
"coverage/",
|
138 |
-
'src/generated/types.ts'
|
139 |
-
],
|
140 |
-
}));
|
141 |
-
|
142 |
-
const otherConfig = {
|
143 |
-
files: ['*.js', '*.ts', '*.cjs'],
|
144 |
-
...js.configs.recommended,
|
145 |
-
...tseslint.configs.recommended,
|
146 |
-
};
|
147 |
-
|
148 |
-
const jsonConfig = {
|
149 |
-
files: ['**/*.json'],
|
150 |
-
language: 'json/json',
|
151 |
-
rules: {
|
152 |
-
'json/no-duplicate-keys': 'error',
|
153 |
-
},
|
154 |
-
};
|
155 |
-
|
156 |
-
export default [
|
157 |
-
{
|
158 |
-
plugins: {
|
159 |
-
json,
|
160 |
-
},
|
161 |
-
},
|
162 |
-
...appConfigs,
|
163 |
-
otherConfig,
|
164 |
-
jsonConfig,
|
165 |
-
];
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
go-web-app-develop/app/index.html
DELETED
@@ -1,69 +0,0 @@
|
|
1 |
-
<!DOCTYPE html>
|
2 |
-
<html
|
3 |
-
lang="en"
|
4 |
-
translate="no"
|
5 |
-
>
|
6 |
-
<head>
|
7 |
-
<meta charset="UTF-8" />
|
8 |
-
<link
|
9 |
-
rel="icon"
|
10 |
-
type="image/svg+xml"
|
11 |
-
href="/go-icon.svg"
|
12 |
-
/>
|
13 |
-
<meta
|
14 |
-
name="viewport"
|
15 |
-
content="width=device-width, initial-scale=1.0"
|
16 |
-
/>
|
17 |
-
<meta
|
18 |
-
name="description"
|
19 |
-
content=""
|
20 |
-
/>
|
21 |
-
<title>
|
22 |
-
%APP_TITLE%
|
23 |
-
</title>
|
24 |
-
<link rel="preconnect" href="https://fonts.googleapis.com">
|
25 |
-
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
26 |
-
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&display=swap" rel="stylesheet">
|
27 |
-
<link href="https://fonts.googleapis.com/css2?family=Montserrat:wght@300;400;500;600;700&display=swap" rel="stylesheet">
|
28 |
-
<link href="https://fonts.googleapis.com/css2?family=Open+Sans:wght@300;400;500;600;700&display=swap" rel="stylesheet">
|
29 |
-
|
30 |
-
<style>
|
31 |
-
html, body {
|
32 |
-
margin: 0;
|
33 |
-
padding: 0;
|
34 |
-
}
|
35 |
-
|
36 |
-
body {
|
37 |
-
font-family: Poppins, system-ui, -apple-system, BlinkMacSystemFont, sans-serif;
|
38 |
-
}
|
39 |
-
|
40 |
-
@media screen {
|
41 |
-
body {
|
42 |
-
background-color: #f7f7f7;
|
43 |
-
}
|
44 |
-
}
|
45 |
-
|
46 |
-
#webapp-preload {
|
47 |
-
width: 100vw;
|
48 |
-
height: 100vh;
|
49 |
-
display: flex;
|
50 |
-
align-items: center;
|
51 |
-
justify-content: center;
|
52 |
-
}
|
53 |
-
</style>
|
54 |
-
</head>
|
55 |
-
<body>
|
56 |
-
<noscript>
|
57 |
-
%APP_TITLE% needs JS.
|
58 |
-
</noscript>
|
59 |
-
<div id="webapp-root">
|
60 |
-
<div id="webapp-preload">
|
61 |
-
%APP_TITLE% loading...
|
62 |
-
</div>
|
63 |
-
</div>
|
64 |
-
<script
|
65 |
-
type="module"
|
66 |
-
src="/src/index.tsx"
|
67 |
-
></script>
|
68 |
-
</body>
|
69 |
-
</html>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
go-web-app-develop/app/package.json
DELETED
@@ -1,119 +0,0 @@
|
|
1 |
-
{
|
2 |
-
"name": "go-web-app",
|
3 |
-
"version": "7.21.0-beta.2",
|
4 |
-
"type": "module",
|
5 |
-
"private": true,
|
6 |
-
"license": "MIT",
|
7 |
-
"repository": {
|
8 |
-
"type": "git",
|
9 |
-
"url": "git+https://github.com/IFRCGo/go-web-app.git",
|
10 |
-
"directory": "app"
|
11 |
-
},
|
12 |
-
"scripts": {
|
13 |
-
"translatte": "tsx scripts/translatte/main.ts",
|
14 |
-
"translatte:generate": "pnpm translatte generate-migration ../translationMigrations ./src/**/i18n.json ../packages/ui/src/**/i18n.json",
|
15 |
-
"translatte:lint": "pnpm translatte lint ./src/**/i18n.json ../packages/ui/src/**/i18n.json",
|
16 |
-
"initialize:type": "mkdir -p generated/ && pnpm initialize:type:go-api && pnpm initialize:type:risk-api",
|
17 |
-
"initialize:type:go-api": "test -f ./generated/types.ts && true || cp types.stub.ts ./generated/types.ts",
|
18 |
-
"initialize:type:risk-api": "test -f ./generated/riskTypes.ts && true || cp types.stub.ts ./generated/riskTypes.ts",
|
19 |
-
"generate:type": "pnpm generate:type:go-api && pnpm generate:type:risk-api",
|
20 |
-
"generate:type:go-api": "dotenv -- cross-var openapi-typescript \"%APP_API_ENDPOINT%api-docs/\" -o ./generated/types.ts --alphabetize",
|
21 |
-
"generate:type:risk-api": "dotenv -- cross-var openapi-typescript \"%APP_RISK_API_ENDPOINT%api-docs/\" -o ./generated/riskTypes.ts --alphabetize",
|
22 |
-
"prestart": "pnpm initialize:type",
|
23 |
-
"start": "pnpm -F @ifrc-go/ui build && vite",
|
24 |
-
"prebuild": "pnpm initialize:type",
|
25 |
-
"build": "pnpm -F @ifrc-go/ui build && vite build",
|
26 |
-
"preview": "vite preview",
|
27 |
-
"pretypecheck": "pnpm initialize:type",
|
28 |
-
"typecheck": "tsc",
|
29 |
-
"prelint:js": "pnpm initialize:type",
|
30 |
-
"lint:js": "eslint src",
|
31 |
-
"lint:css": "stylelint \"./src/**/*.css\"",
|
32 |
-
"lint:translation": "pnpm translatte:lint",
|
33 |
-
"lint": "pnpm lint:js && pnpm lint:css && pnpm lint:translation",
|
34 |
-
"lint:fix": "pnpm lint:js --fix && pnpm lint:css --fix",
|
35 |
-
"test": "vitest",
|
36 |
-
"test:coverage": "vitest run --coverage",
|
37 |
-
"surge:deploy": "branch=$(git rev-parse --symbolic-full-name --abbrev-ref HEAD); branch=$(echo $branch | tr ./ -); cp ../build/index.html ../build/200.html; surge -p ../build/ -d https://ifrc-go-$branch.surge.sh",
|
38 |
-
"surge:teardown": "branch=$(git rev-parse --symbolic-full-name --abbrev-ref HEAD); branch=$(echo $branch | tr ./ -); surge teardown https://ifrc-go-$branch.surge.sh"
|
39 |
-
},
|
40 |
-
"dependencies": {
|
41 |
-
"@ifrc-go/icons": "^2.0.1",
|
42 |
-
"@ifrc-go/ui": "workspace:^",
|
43 |
-
"@sentry/react": "^7.81.1",
|
44 |
-
"@tinymce/tinymce-react": "^5.1.1",
|
45 |
-
"@togglecorp/fujs": "^2.1.1",
|
46 |
-
"@togglecorp/re-map": "^0.3.0",
|
47 |
-
"@togglecorp/toggle-form": "^2.0.4",
|
48 |
-
"@togglecorp/toggle-request": "^1.0.0-beta.3",
|
49 |
-
"@turf/bbox": "^6.5.0",
|
50 |
-
"@turf/buffer": "^6.5.0",
|
51 |
-
"exceljs": "^4.3.0",
|
52 |
-
"file-saver": "^2.0.5",
|
53 |
-
"html-to-image": "^1.11.11",
|
54 |
-
"mapbox-gl": "^1.13.0",
|
55 |
-
"papaparse": "^5.4.1",
|
56 |
-
"react": "^18.2.0",
|
57 |
-
"react-dom": "^18.2.0",
|
58 |
-
"react-router-dom": "^6.18.0",
|
59 |
-
"sanitize-html": "^2.10.0"
|
60 |
-
},
|
61 |
-
"devDependencies": {
|
62 |
-
"@eslint/eslintrc": "^3.1.0",
|
63 |
-
"@eslint/js": "^9.20.0",
|
64 |
-
"@eslint/json": "^0.5.0",
|
65 |
-
"@julr/vite-plugin-validate-env": "^1.0.1",
|
66 |
-
"@types/file-saver": "^2.0.5",
|
67 |
-
"@types/mapbox-gl": "^1.13.0",
|
68 |
-
"@types/node": "^20.11.6",
|
69 |
-
"@types/papaparse": "^5.3.8",
|
70 |
-
"@types/react": "^18.0.28",
|
71 |
-
"@types/react-dom": "^18.0.11",
|
72 |
-
"@types/sanitize-html": "^2.9.0",
|
73 |
-
"@types/yargs": "^17.0.32",
|
74 |
-
"@typescript-eslint/eslint-plugin": "^8.11.0",
|
75 |
-
"@typescript-eslint/parser": "^8.11.0",
|
76 |
-
"@vitejs/plugin-react-swc": "^3.5.0",
|
77 |
-
"@vitest/coverage-v8": "^1.2.2",
|
78 |
-
"autoprefixer": "^10.4.14",
|
79 |
-
"cross-var": "^1.1.0",
|
80 |
-
"dotenv-cli": "^7.4.2",
|
81 |
-
"eslint": "^9.20.1",
|
82 |
-
"eslint-config-airbnb": "^19.0.4",
|
83 |
-
"eslint-import-resolver-typescript": "^3.6.3",
|
84 |
-
"eslint-plugin-import": "^2.31.0",
|
85 |
-
"eslint-plugin-import-exports-imports-resolver": "^1.0.1",
|
86 |
-
"eslint-plugin-import-newlines": "^1.3.4",
|
87 |
-
"eslint-plugin-jsx-a11y": "^6.10.1",
|
88 |
-
"eslint-plugin-react": "^7.37.4",
|
89 |
-
"eslint-plugin-react-hooks": "^5.0.0",
|
90 |
-
"eslint-plugin-react-refresh": "^0.4.13",
|
91 |
-
"eslint-plugin-simple-import-sort": "^12.1.1",
|
92 |
-
"fast-glob": "^3.3.2",
|
93 |
-
"happy-dom": "^9.18.3",
|
94 |
-
"openapi-typescript": "6.5.5",
|
95 |
-
"postcss": "^8.5.3",
|
96 |
-
"postcss-nested": "^7.0.2",
|
97 |
-
"postcss-normalize": "^13.0.1",
|
98 |
-
"postcss-preset-env": "^10.1.5",
|
99 |
-
"rollup-plugin-visualizer": "^5.9.0",
|
100 |
-
"stylelint": "^16.17.0",
|
101 |
-
"stylelint-config-concentric": "^2.0.2",
|
102 |
-
"stylelint-config-recommended": "^15.0.0",
|
103 |
-
"stylelint-value-no-unknown-custom-properties": "^6.0.1",
|
104 |
-
"surge": "^0.23.1",
|
105 |
-
"ts-md5": "^1.3.1",
|
106 |
-
"tsx": "^4.7.2",
|
107 |
-
"typescript": "^5.5.2",
|
108 |
-
"typescript-eslint": "^8.26.0",
|
109 |
-
"vite": "^5.0.10",
|
110 |
-
"vite-plugin-checker": "^0.7.0",
|
111 |
-
"vite-plugin-compression2": "^0.11.0",
|
112 |
-
"vite-plugin-radar": "^0.9.2",
|
113 |
-
"vite-plugin-svgr": "^4.2.0",
|
114 |
-
"vite-plugin-webfont-dl": "^3.9.4",
|
115 |
-
"vite-tsconfig-paths": "^4.2.2",
|
116 |
-
"vitest": "^1.2.2",
|
117 |
-
"yargs": "^17.7.2"
|
118 |
-
}
|
119 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
go-web-app-develop/app/postcss.config.cjs
DELETED
@@ -1,8 +0,0 @@
|
|
1 |
-
module.exports = {
|
2 |
-
plugins: [
|
3 |
-
require('postcss-preset-env'),
|
4 |
-
require('postcss-nested'),
|
5 |
-
require('postcss-normalize'),
|
6 |
-
require('autoprefixer'),
|
7 |
-
],
|
8 |
-
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
go-web-app-develop/app/public/go-icon.svg
DELETED
go-web-app-develop/app/scripts/translatte/README.md
DELETED
@@ -1,59 +0,0 @@
|
|
1 |
-
# translatte
|
2 |
-
|
3 |
-
A simple script to synchronize translations in source code to translations in
|
4 |
-
server
|
5 |
-
|
6 |
-
## Usecase
|
7 |
-
|
8 |
-
### Generating migrations
|
9 |
-
|
10 |
-
When adding a new feature or updating existing feature or removing an
|
11 |
-
existing feature on the codebase, we may need to update the strings used
|
12 |
-
in the application.
|
13 |
-
|
14 |
-
Developers can change the translations using their preferred choice of editor.
|
15 |
-
|
16 |
-
Once all of the changes have been made, we can generate a migration file for the translations using:
|
17 |
-
|
18 |
-
```bash
|
19 |
-
pnpm translatte generate-migration ./src/translationMigrations ./src/**/i18n.json
|
20 |
-
```
|
21 |
-
|
22 |
-
Once the migration file has been created, the migration file can be committed to the VCS.
|
23 |
-
|
24 |
-
### Applying migrations
|
25 |
-
|
26 |
-
When we are deploying the changes to the server, we will need to update
|
27 |
-
the strings in the server.
|
28 |
-
|
29 |
-
We can generate the new set of strings for the server using:
|
30 |
-
|
31 |
-
```bash
|
32 |
-
pnpm translatte apply-migrations ./src/translationMigrations --last-migration "name_of_last_migration" --source "strings_json_from_server.json" --destination "new_strings_json_for_server.json"
|
33 |
-
```
|
34 |
-
|
35 |
-
### Merge migrations
|
36 |
-
|
37 |
-
Once the migrations are applied to the strings in the server, we can merge the migrations into a single file.
|
38 |
-
|
39 |
-
To merge migrations, we can run the following command:
|
40 |
-
|
41 |
-
```bash
|
42 |
-
pnpm translatte merge-migrations ./src/translationMigrations --from 'initial_migration.json' --to 'final_migration.json'
|
43 |
-
```
|
44 |
-
|
45 |
-
### Checking migrations
|
46 |
-
|
47 |
-
We can use the following command to check for valid migrations:
|
48 |
-
|
49 |
-
```bash
|
50 |
-
pnpm translatte lint ./src/**/i18n.json
|
51 |
-
```
|
52 |
-
|
53 |
-
### Listing migrations
|
54 |
-
|
55 |
-
We can use the following command to list all migrations:
|
56 |
-
|
57 |
-
```bash
|
58 |
-
pnpm translatte list-migrations ./src/translationMigrations
|
59 |
-
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
go-web-app-develop/app/scripts/translatte/commands/applyMigrations.test.ts
DELETED
@@ -1,104 +0,0 @@
|
|
1 |
-
import { expect } from 'vitest';
|
2 |
-
import { mkdirSync } from 'fs';
|
3 |
-
import { join } from 'path';
|
4 |
-
|
5 |
-
import { testWithTmpDir } from '../testHelpers';
|
6 |
-
import {
|
7 |
-
writeFilePromisify,
|
8 |
-
readJsonFilesContents,
|
9 |
-
} from '../utils';
|
10 |
-
import {
|
11 |
-
migrationContent1,
|
12 |
-
migrationContent2,
|
13 |
-
migrationContent3,
|
14 |
-
migrationContent4,
|
15 |
-
migrationContent5,
|
16 |
-
migrationContent6,
|
17 |
-
|
18 |
-
strings1,
|
19 |
-
strings2,
|
20 |
-
} from '../mockData';
|
21 |
-
import applyMigrations from './applyMigrations';
|
22 |
-
import { SourceFileContent } from '../types';
|
23 |
-
|
24 |
-
testWithTmpDir('test applyMigrations with no data in server', async ({ tmpdir }) => {
|
25 |
-
mkdirSync(join(tmpdir, 'migrations'));
|
26 |
-
const migrations = [
|
27 |
-
{ name: '000001-1000000000000.json', content: migrationContent1 },
|
28 |
-
{ name: '000002-1000000000000.json', content: migrationContent2 },
|
29 |
-
{ name: '000003-1000000000000.json', content: migrationContent3 },
|
30 |
-
{ name: '000004-1000000000000.json', content: migrationContent4 },
|
31 |
-
{ name: '000005-1000000000000.json', content: migrationContent5 },
|
32 |
-
].map(({ name, content }) => writeFilePromisify(
|
33 |
-
join(tmpdir, 'migrations', name),
|
34 |
-
JSON.stringify(content, null, 4),
|
35 |
-
'utf8',
|
36 |
-
));
|
37 |
-
await Promise.all(migrations);
|
38 |
-
|
39 |
-
mkdirSync(join(tmpdir, 'strings'));
|
40 |
-
|
41 |
-
const emptySourceFile: SourceFileContent = {
|
42 |
-
last_migration: undefined,
|
43 |
-
strings: [],
|
44 |
-
};
|
45 |
-
await writeFilePromisify(
|
46 |
-
join(tmpdir, 'strings', 'before.json'),
|
47 |
-
JSON.stringify(emptySourceFile),
|
48 |
-
'utf8',
|
49 |
-
);
|
50 |
-
|
51 |
-
await applyMigrations(
|
52 |
-
tmpdir,
|
53 |
-
join(tmpdir, 'strings', 'before.json'),
|
54 |
-
join(tmpdir, 'strings', 'after.json'),
|
55 |
-
'migrations',
|
56 |
-
['np'],
|
57 |
-
undefined,
|
58 |
-
false,
|
59 |
-
);
|
60 |
-
|
61 |
-
const newSourceFiles = await readJsonFilesContents([
|
62 |
-
join(tmpdir, 'strings', 'after.json'),
|
63 |
-
]);
|
64 |
-
const newSourceFileContent = newSourceFiles[0].content;
|
65 |
-
|
66 |
-
expect(newSourceFileContent).toEqual(strings1)
|
67 |
-
});
|
68 |
-
|
69 |
-
testWithTmpDir('test applyMigrations with data in server', async ({ tmpdir }) => {
|
70 |
-
mkdirSync(join(tmpdir, 'migrations'));
|
71 |
-
const migrations = [
|
72 |
-
{ name: '000006-1000000000000.json', content: migrationContent6 },
|
73 |
-
].map(({ name, content }) => writeFilePromisify(
|
74 |
-
join(tmpdir, 'migrations', name),
|
75 |
-
JSON.stringify(content, null, 4),
|
76 |
-
'utf8',
|
77 |
-
));
|
78 |
-
await Promise.all(migrations);
|
79 |
-
|
80 |
-
mkdirSync(join(tmpdir, 'strings'));
|
81 |
-
|
82 |
-
await writeFilePromisify(
|
83 |
-
join(tmpdir, 'strings', 'before.json'),
|
84 |
-
JSON.stringify(strings1),
|
85 |
-
'utf8',
|
86 |
-
);
|
87 |
-
|
88 |
-
await applyMigrations(
|
89 |
-
tmpdir,
|
90 |
-
join(tmpdir, 'strings', 'before.json'),
|
91 |
-
join(tmpdir, 'strings', 'after.json'),
|
92 |
-
'migrations',
|
93 |
-
['np'],
|
94 |
-
undefined,
|
95 |
-
false,
|
96 |
-
);
|
97 |
-
|
98 |
-
const newSourceFiles = await readJsonFilesContents([
|
99 |
-
join(tmpdir, 'strings', 'after.json'),
|
100 |
-
]);
|
101 |
-
const newSourceFileContent = newSourceFiles[0].content;
|
102 |
-
|
103 |
-
expect(newSourceFileContent).toEqual(strings2)
|
104 |
-
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
go-web-app-develop/app/scripts/translatte/commands/applyMigrations.ts
DELETED
@@ -1,177 +0,0 @@
|
|
1 |
-
import { Md5 } from 'ts-md5';
|
2 |
-
import { listToMap, isDefined, unique } from '@togglecorp/fujs';
|
3 |
-
import { isAbsolute, join, basename } from 'path';
|
4 |
-
import {
|
5 |
-
readSource,
|
6 |
-
getMigrationFilesAttrs,
|
7 |
-
readMigrations,
|
8 |
-
writeFilePromisify,
|
9 |
-
} from '../utils';
|
10 |
-
import { merge } from './mergeMigrations';
|
11 |
-
import {
|
12 |
-
SourceFileContent,
|
13 |
-
MigrationFileContent,
|
14 |
-
SourceStringItem,
|
15 |
-
} from '../types';
|
16 |
-
|
17 |
-
function apply(
|
18 |
-
strings: SourceStringItem[],
|
19 |
-
migrationActions: MigrationFileContent['actions'],
|
20 |
-
languages: string[],
|
21 |
-
): SourceStringItem[] {
|
22 |
-
const stringsMapping = listToMap(
|
23 |
-
strings,
|
24 |
-
(item) => `${item.page_name}:${item.key}:${item.language}` as string,
|
25 |
-
(item) => item,
|
26 |
-
);
|
27 |
-
|
28 |
-
const newMapping: {
|
29 |
-
[key: string]: SourceStringItem | null;
|
30 |
-
} = { };
|
31 |
-
|
32 |
-
unique(['en', ...languages]).forEach((language) => {
|
33 |
-
migrationActions.forEach((action) => {
|
34 |
-
const isSourceLanguage = language === 'en';
|
35 |
-
const key = `${action.namespace}:${action.key}:${language}`;
|
36 |
-
if (action.action === 'add') {
|
37 |
-
const hash = Md5.hashStr(action.value);
|
38 |
-
|
39 |
-
const prevValue = stringsMapping[key];
|
40 |
-
// NOTE: we are comparing hash instead of value so that this works for source language as well as other languages
|
41 |
-
if (prevValue && prevValue.hash !== hash) {
|
42 |
-
throw `Add: We already have string with different value for namespace '${action.namespace}' and key '${action.key}'`;
|
43 |
-
}
|
44 |
-
|
45 |
-
if (newMapping[key]) {
|
46 |
-
throw `Add: We already have string for namespace '${action.namespace}' and key '${action.key}' in migration`;
|
47 |
-
}
|
48 |
-
|
49 |
-
newMapping[key] = {
|
50 |
-
hash,
|
51 |
-
key: action.key,
|
52 |
-
page_name: action.namespace,
|
53 |
-
language,
|
54 |
-
value: isSourceLanguage
|
55 |
-
? action.value
|
56 |
-
: '',
|
57 |
-
};
|
58 |
-
} else if (action.action === 'remove') {
|
59 |
-
// NOTE: We can add or move string so we might have value in newMapping
|
60 |
-
if (!newMapping[key]) {
|
61 |
-
newMapping[key] = null;
|
62 |
-
}
|
63 |
-
} else {
|
64 |
-
const prevValue = stringsMapping[key];
|
65 |
-
if (!prevValue) {
|
66 |
-
throw `Update: We do not have string with namespace '${action.namespace}' and key '${action.key}'`;
|
67 |
-
}
|
68 |
-
|
69 |
-
const newKey = action.newKey ?? prevValue.key;
|
70 |
-
const newNamespace = action.newNamespace ?? prevValue.page_name;
|
71 |
-
const newValue = isSourceLanguage
|
72 |
-
? action.newValue ?? prevValue.value
|
73 |
-
: prevValue.value;
|
74 |
-
const newHash = isSourceLanguage
|
75 |
-
? Md5.hashStr(newValue)
|
76 |
-
: prevValue.hash;
|
77 |
-
|
78 |
-
const newCanonicalKey = `${newNamespace}:${newKey}:${language}`;
|
79 |
-
|
80 |
-
|
81 |
-
// NOTE: remove the old key and add new key
|
82 |
-
if (!newMapping[key]) {
|
83 |
-
newMapping[key] = null;
|
84 |
-
}
|
85 |
-
|
86 |
-
const newItem = {
|
87 |
-
hash: newHash,
|
88 |
-
key: newKey,
|
89 |
-
page_name: newNamespace,
|
90 |
-
language,
|
91 |
-
value: newValue,
|
92 |
-
}
|
93 |
-
|
94 |
-
if (newMapping[newCanonicalKey]) {
|
95 |
-
throw `Update: We already have string for namespace '${action.namespace}' and key '${action.key}' in migration`;
|
96 |
-
}
|
97 |
-
newMapping[newCanonicalKey] = newItem;
|
98 |
-
}
|
99 |
-
});
|
100 |
-
});
|
101 |
-
|
102 |
-
const finalMapping: typeof newMapping = {
|
103 |
-
...stringsMapping,
|
104 |
-
...newMapping,
|
105 |
-
};
|
106 |
-
|
107 |
-
return Object.values(finalMapping)
|
108 |
-
.filter(isDefined)
|
109 |
-
.sort((foo, bar) => (
|
110 |
-
foo.page_name.localeCompare(bar.page_name)
|
111 |
-
|| foo.key.localeCompare(bar.key)
|
112 |
-
|| foo.language.localeCompare(bar.language)
|
113 |
-
))
|
114 |
-
}
|
115 |
-
|
116 |
-
async function applyMigrations(
|
117 |
-
projectPath: string,
|
118 |
-
sourceFileName: string,
|
119 |
-
destinationFileName: string,
|
120 |
-
migrationFilePath: string,
|
121 |
-
languages: string[],
|
122 |
-
from: string | undefined,
|
123 |
-
dryRun: boolean | undefined,
|
124 |
-
) {
|
125 |
-
const sourcePath = isAbsolute(sourceFileName)
|
126 |
-
? sourceFileName
|
127 |
-
: join(projectPath, sourceFileName)
|
128 |
-
const sourceFile = await readSource(sourcePath)
|
129 |
-
|
130 |
-
const migrationFilesAttrs = await getMigrationFilesAttrs(projectPath, migrationFilePath);
|
131 |
-
const selectedMigrationFilesAttrs = from
|
132 |
-
? migrationFilesAttrs.filter((item) => (item.migrationName > from))
|
133 |
-
: migrationFilesAttrs;
|
134 |
-
|
135 |
-
console.info(`Found ${selectedMigrationFilesAttrs.length} migration files`);
|
136 |
-
|
137 |
-
if (selectedMigrationFilesAttrs.length < 1) {
|
138 |
-
throw 'There should be at least 1 migration file';
|
139 |
-
}
|
140 |
-
|
141 |
-
const selectedMigrations = await readMigrations(
|
142 |
-
selectedMigrationFilesAttrs.map((migration) => migration.fileName),
|
143 |
-
);
|
144 |
-
|
145 |
-
const lastMigration = selectedMigrations[selectedMigrations.length - 1];
|
146 |
-
|
147 |
-
const mergedMigrationActions = merge(
|
148 |
-
selectedMigrations.map((migration) => migration.content),
|
149 |
-
);
|
150 |
-
|
151 |
-
const outputSourceFileContent: SourceFileContent = {
|
152 |
-
...sourceFile.content,
|
153 |
-
last_migration: basename(lastMigration.file),
|
154 |
-
strings: apply(
|
155 |
-
sourceFile.content.strings,
|
156 |
-
mergedMigrationActions,
|
157 |
-
languages,
|
158 |
-
),
|
159 |
-
};
|
160 |
-
|
161 |
-
const destinationPath = isAbsolute(destinationFileName)
|
162 |
-
? destinationFileName
|
163 |
-
: join(projectPath, destinationFileName)
|
164 |
-
|
165 |
-
if (dryRun) {
|
166 |
-
console.info(`Creating file '${destinationPath}'`);
|
167 |
-
console.info(outputSourceFileContent);
|
168 |
-
} else {
|
169 |
-
await writeFilePromisify(
|
170 |
-
destinationPath,
|
171 |
-
JSON.stringify(outputSourceFileContent, null, 4),
|
172 |
-
'utf8',
|
173 |
-
);
|
174 |
-
}
|
175 |
-
}
|
176 |
-
|
177 |
-
export default applyMigrations;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
go-web-app-develop/app/scripts/translatte/commands/exportMigration.ts
DELETED
@@ -1,62 +0,0 @@
|
|
1 |
-
import xlsx from 'exceljs';
|
2 |
-
|
3 |
-
import { readMigrations } from '../utils';
|
4 |
-
import { isNotDefined } from '@togglecorp/fujs';
|
5 |
-
|
6 |
-
async function exportMigration(
|
7 |
-
migrationFilePath: string,
|
8 |
-
exportFileName: string,
|
9 |
-
) {
|
10 |
-
const migrations = await readMigrations(
|
11 |
-
[migrationFilePath]
|
12 |
-
);
|
13 |
-
|
14 |
-
const actions = migrations[0].content.actions;
|
15 |
-
const workbook = new xlsx.Workbook();
|
16 |
-
const now = new Date();
|
17 |
-
workbook.created = now;
|
18 |
-
|
19 |
-
const yyyy = now.getFullYear();
|
20 |
-
const mm = (now.getMonth() + 1).toString().padStart(2, '0');
|
21 |
-
const dd = now.getDate().toString().padStart(2, '0');
|
22 |
-
const worksheet = workbook.addWorksheet(
|
23 |
-
`${yyyy}-${mm}-${dd}`
|
24 |
-
);
|
25 |
-
|
26 |
-
worksheet.columns = [
|
27 |
-
{ header: 'Namespace', key: 'namespace' },
|
28 |
-
{ header: 'Key', key: 'key' },
|
29 |
-
{ header: 'EN', key: 'en' },
|
30 |
-
{ header: 'FR', key: 'fr' },
|
31 |
-
{ header: 'ES', key: 'es' },
|
32 |
-
{ header: 'AR', key: 'ar' },
|
33 |
-
]
|
34 |
-
|
35 |
-
actions.forEach((actionItem) => {
|
36 |
-
if (actionItem.action === 'remove') {
|
37 |
-
return;
|
38 |
-
}
|
39 |
-
|
40 |
-
if (actionItem.action === 'update' && isNotDefined(actionItem.newValue)) {
|
41 |
-
return;
|
42 |
-
}
|
43 |
-
|
44 |
-
const value = actionItem.action === 'update'
|
45 |
-
? actionItem.newValue
|
46 |
-
: actionItem.value;
|
47 |
-
|
48 |
-
worksheet.addRow({
|
49 |
-
namespace: actionItem.namespace,
|
50 |
-
key: actionItem.key,
|
51 |
-
en: value,
|
52 |
-
});
|
53 |
-
});
|
54 |
-
|
55 |
-
const fileName = isNotDefined(exportFileName)
|
56 |
-
? `go-strings-${yyyy}-${mm}-${dd}`
|
57 |
-
: exportFileName;
|
58 |
-
|
59 |
-
await workbook.xlsx.writeFile(`${fileName}.xlsx`);
|
60 |
-
}
|
61 |
-
|
62 |
-
export default exportMigration;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
go-web-app-develop/app/scripts/translatte/commands/generateMigration.test.ts
DELETED
@@ -1,102 +0,0 @@
|
|
1 |
-
import { expect } from 'vitest';
|
2 |
-
import { mkdirSync } from 'fs';
|
3 |
-
import { join } from 'path';
|
4 |
-
|
5 |
-
import generateMigration from './generateMigration';
|
6 |
-
import { testWithTmpDir } from '../testHelpers';
|
7 |
-
import { writeFilePromisify, readMigrations } from '../utils';
|
8 |
-
import {
|
9 |
-
migrationContent1,
|
10 |
-
migrationContent2,
|
11 |
-
migrationContent3,
|
12 |
-
migrationContent4,
|
13 |
-
migrationContent5,
|
14 |
-
loginContent,
|
15 |
-
registerContent,
|
16 |
-
updatedLoginContent,
|
17 |
-
updatedRegisterContent,
|
18 |
-
migrationContent6,
|
19 |
-
} from '../mockData';
|
20 |
-
|
21 |
-
|
22 |
-
testWithTmpDir('test generateMigration with no change', async ({ tmpdir }) => {
|
23 |
-
mkdirSync(join(tmpdir, 'migrations'));
|
24 |
-
const migrations = [
|
25 |
-
{ name: '000001-1000000000000.json', content: migrationContent1 },
|
26 |
-
{ name: '000002-1000000000000.json', content: migrationContent2 },
|
27 |
-
{ name: '000003-1000000000000.json', content: migrationContent3 },
|
28 |
-
{ name: '000004-1000000000000.json', content: migrationContent4 },
|
29 |
-
{ name: '000005-1000000000000.json', content: migrationContent5 },
|
30 |
-
].map(({ name, content }) => writeFilePromisify(
|
31 |
-
join(tmpdir, 'migrations', name),
|
32 |
-
JSON.stringify(content, null, 4),
|
33 |
-
'utf8',
|
34 |
-
));
|
35 |
-
await Promise.all(migrations);
|
36 |
-
|
37 |
-
mkdirSync(join(tmpdir, 'src'));
|
38 |
-
const translations = [
|
39 |
-
{ name: 'home.i18n.json', content: loginContent },
|
40 |
-
{ name: 'register.i18n.json', content: registerContent },
|
41 |
-
].map(({ name, content }) => writeFilePromisify(
|
42 |
-
join(tmpdir, 'src', name),
|
43 |
-
JSON.stringify(content, null, 4),
|
44 |
-
'utf8',
|
45 |
-
));
|
46 |
-
await Promise.all(translations);
|
47 |
-
|
48 |
-
await expect(
|
49 |
-
() => generateMigration(
|
50 |
-
tmpdir,
|
51 |
-
'migrations',
|
52 |
-
'src/**/*.i18n.json',
|
53 |
-
new Date().getTime(),
|
54 |
-
false,
|
55 |
-
),
|
56 |
-
).rejects.toThrow('Nothing to do');
|
57 |
-
});
|
58 |
-
|
59 |
-
testWithTmpDir('test generateMigration with change', async ({ tmpdir }) => {
|
60 |
-
mkdirSync(join(tmpdir, 'migrations'));
|
61 |
-
const migrations = [
|
62 |
-
{ name: '000001-1000000000000.json', content: migrationContent1 },
|
63 |
-
{ name: '000002-1000000000000.json', content: migrationContent2 },
|
64 |
-
{ name: '000003-1000000000000.json', content: migrationContent3 },
|
65 |
-
{ name: '000004-1000000000000.json', content: migrationContent4 },
|
66 |
-
{ name: '000005-1000000000000.json', content: migrationContent5 },
|
67 |
-
].map(({ name, content }) => writeFilePromisify(
|
68 |
-
join(tmpdir, 'migrations', name),
|
69 |
-
JSON.stringify(content, null, 4),
|
70 |
-
'utf8',
|
71 |
-
));
|
72 |
-
await Promise.all(migrations);
|
73 |
-
|
74 |
-
mkdirSync(join(tmpdir, 'src'));
|
75 |
-
|
76 |
-
const translations = [
|
77 |
-
{ name: 'home.i18n.json', content: updatedLoginContent },
|
78 |
-
{ name: 'register.i18n.json', content: updatedRegisterContent },
|
79 |
-
].map(({ name, content }) => writeFilePromisify(
|
80 |
-
join(tmpdir, 'src', name),
|
81 |
-
JSON.stringify(content, null, 4),
|
82 |
-
'utf8',
|
83 |
-
));
|
84 |
-
await Promise.all(translations);
|
85 |
-
|
86 |
-
const timestamp = new Date().getTime();
|
87 |
-
|
88 |
-
await generateMigration(
|
89 |
-
tmpdir,
|
90 |
-
'migrations',
|
91 |
-
'src/**/*.i18n.json',
|
92 |
-
timestamp,
|
93 |
-
false,
|
94 |
-
);
|
95 |
-
|
96 |
-
const generatedMigrations = await readMigrations([
|
97 |
-
join(tmpdir, 'migrations', `000006-${timestamp}.json`)
|
98 |
-
]);
|
99 |
-
const generatedMigrationContent = generatedMigrations[0].content;
|
100 |
-
|
101 |
-
expect(generatedMigrationContent).toEqual(migrationContent6)
|
102 |
-
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
go-web-app-develop/app/scripts/translatte/commands/generateMigration.ts
DELETED
@@ -1,195 +0,0 @@
|
|
1 |
-
import { Md5 } from 'ts-md5';
|
2 |
-
import { join, isAbsolute } from 'path';
|
3 |
-
|
4 |
-
import {
|
5 |
-
writeFilePromisify,
|
6 |
-
oneOneMapping,
|
7 |
-
readTranslations,
|
8 |
-
getTranslationFileNames,
|
9 |
-
getMigrationFilesAttrs,
|
10 |
-
readMigrations,
|
11 |
-
oneOneMappingNonUnique,
|
12 |
-
} from '../utils';
|
13 |
-
import { MigrationActionItem, MigrationFileContent } from '../types';
|
14 |
-
import { merge } from './mergeMigrations';
|
15 |
-
|
16 |
-
function getCombinedKey(key: string, namespace: string) {
|
17 |
-
return `${namespace}:${key}`;
|
18 |
-
}
|
19 |
-
|
20 |
-
type StateItem = {
|
21 |
-
filename?: string;
|
22 |
-
namespace: string;
|
23 |
-
key: string;
|
24 |
-
value: string;
|
25 |
-
}
|
26 |
-
|
27 |
-
// FIXME: The output should be stable
|
28 |
-
function generateMigration(
|
29 |
-
prevState: StateItem[],
|
30 |
-
currentState: StateItem[],
|
31 |
-
): MigrationActionItem[] {
|
32 |
-
/*
|
33 |
-
console.info('prevState length', prevState.length);
|
34 |
-
console.info('currentState length', currentState.length);
|
35 |
-
console.info('Total change', Math.abs(prevState.length - currentState.length));
|
36 |
-
*/
|
37 |
-
|
38 |
-
const {
|
39 |
-
// Same, key, namespace and same value
|
40 |
-
validCommonItems: identicalStateItems,
|
41 |
-
|
42 |
-
// Same, key, namespace but different value
|
43 |
-
invalidCommonItems: valueUpdatedStateItems,
|
44 |
-
|
45 |
-
// items with different key or namespace or both
|
46 |
-
prevStateRemainder: potentiallyRemovedStateItems,
|
47 |
-
|
48 |
-
// items with different key or namespace or both
|
49 |
-
currentStateRemainder: potentiallyAddedStateItems,
|
50 |
-
} = oneOneMapping(
|
51 |
-
prevState,
|
52 |
-
currentState,
|
53 |
-
({ key, namespace }) => getCombinedKey(key, namespace),
|
54 |
-
(prev, current) => prev.value === current.value,
|
55 |
-
);
|
56 |
-
|
57 |
-
console.info(`Unchanged strings: ${identicalStateItems.length}`)
|
58 |
-
console.info(`Value updated strings: ${valueUpdatedStateItems.length}`)
|
59 |
-
|
60 |
-
console.info(`Potentially removed: ${potentiallyRemovedStateItems.length}`)
|
61 |
-
console.info(`Potentially added: ${potentiallyAddedStateItems.length}`)
|
62 |
-
|
63 |
-
const {
|
64 |
-
commonItems: namespaceUpdatedStateItems,
|
65 |
-
prevStateRemainder: potentiallyRemovedStateItemsAfterNamespaceChange,
|
66 |
-
currentStateRemainder: potentiallyAddedStateItemsAfterNamespaceChange,
|
67 |
-
} = oneOneMappingNonUnique(
|
68 |
-
potentiallyRemovedStateItems,
|
69 |
-
potentiallyAddedStateItems,
|
70 |
-
(item) => getCombinedKey(item.key, Md5.hashStr(item.value)),
|
71 |
-
);
|
72 |
-
|
73 |
-
const {
|
74 |
-
commonItems: keyUpdatedStateItems,
|
75 |
-
prevStateRemainder: removedStateItems,
|
76 |
-
currentStateRemainder: addedStateItems,
|
77 |
-
} = oneOneMappingNonUnique(
|
78 |
-
potentiallyRemovedStateItemsAfterNamespaceChange,
|
79 |
-
potentiallyAddedStateItemsAfterNamespaceChange,
|
80 |
-
(item) => getCombinedKey(item.namespace, Md5.hashStr(item.value)),
|
81 |
-
);
|
82 |
-
|
83 |
-
console.info(`Namespace updated strings: ${namespaceUpdatedStateItems.length}`)
|
84 |
-
console.info(`Added strings: ${addedStateItems.length}`)
|
85 |
-
console.info(`Removed strings: ${removedStateItems.length}`)
|
86 |
-
|
87 |
-
return [
|
88 |
-
...valueUpdatedStateItems.map(({ prevStateItem, currentStateItem }) => ({
|
89 |
-
action: 'update' as const,
|
90 |
-
key: prevStateItem.key,
|
91 |
-
namespace: prevStateItem.namespace,
|
92 |
-
newValue: currentStateItem.value,
|
93 |
-
})),
|
94 |
-
...namespaceUpdatedStateItems.map(({ prevStateItem, currentStateItem }) => ({
|
95 |
-
action: 'update' as const,
|
96 |
-
key: prevStateItem.key,
|
97 |
-
namespace: prevStateItem.namespace,
|
98 |
-
newNamespace: currentStateItem.namespace,
|
99 |
-
})),
|
100 |
-
...keyUpdatedStateItems.map(({ prevStateItem, currentStateItem }) => ({
|
101 |
-
action: 'update' as const,
|
102 |
-
key: prevStateItem.key,
|
103 |
-
newKey: currentStateItem.key,
|
104 |
-
namespace: prevStateItem.namespace,
|
105 |
-
})),
|
106 |
-
...addedStateItems.map((item) => ({
|
107 |
-
action: 'add' as const,
|
108 |
-
key: item.key,
|
109 |
-
namespace: item.namespace,
|
110 |
-
value: item.value,
|
111 |
-
})),
|
112 |
-
...removedStateItems.map((item) => ({
|
113 |
-
action: 'remove' as const,
|
114 |
-
key: item.key,
|
115 |
-
namespace: item.namespace,
|
116 |
-
})),
|
117 |
-
].sort((foo, bar) => (
|
118 |
-
foo.namespace.localeCompare(bar.namespace)
|
119 |
-
|| foo.action.localeCompare(bar.action)
|
120 |
-
|| foo.key.localeCompare(bar.key)
|
121 |
-
));
|
122 |
-
}
|
123 |
-
|
124 |
-
async function generate(
|
125 |
-
projectPath: string,
|
126 |
-
migrationFilePath: string,
|
127 |
-
translationFileName: string | string[],
|
128 |
-
timestamp: number,
|
129 |
-
dryRun: boolean | undefined,
|
130 |
-
) {
|
131 |
-
const migrationFilesAttrs = await getMigrationFilesAttrs(projectPath, migrationFilePath);
|
132 |
-
const selectedMigrationFilesAttrs = migrationFilesAttrs;
|
133 |
-
console.info(`Found ${selectedMigrationFilesAttrs.length} migration files`);
|
134 |
-
const selectedMigrations = await readMigrations(
|
135 |
-
selectedMigrationFilesAttrs.map((migration) => migration.fileName),
|
136 |
-
);
|
137 |
-
const mergedMigrationActions = merge(
|
138 |
-
selectedMigrations.map((migration) => migration.content),
|
139 |
-
);
|
140 |
-
|
141 |
-
const serverState: StateItem[] = mergedMigrationActions.map((item) => {
|
142 |
-
if (item.action !== 'add') {
|
143 |
-
throw `The action should be "add" but found "${item.action}"`;
|
144 |
-
}
|
145 |
-
return {
|
146 |
-
filename: undefined,
|
147 |
-
namespace: item.namespace,
|
148 |
-
key: item.key,
|
149 |
-
value: item.value,
|
150 |
-
}
|
151 |
-
});
|
152 |
-
const translationFiles = await getTranslationFileNames(
|
153 |
-
projectPath,
|
154 |
-
Array.isArray(translationFileName) ? translationFileName : [translationFileName],
|
155 |
-
);
|
156 |
-
const { translations } = await readTranslations(translationFiles);
|
157 |
-
const fileState = translations.map((item) => ({
|
158 |
-
...item,
|
159 |
-
}));
|
160 |
-
|
161 |
-
const migrationActionItems = generateMigration(
|
162 |
-
serverState,
|
163 |
-
fileState,
|
164 |
-
);
|
165 |
-
|
166 |
-
if (migrationActionItems.length <= 0) {
|
167 |
-
throw 'Nothing to do';
|
168 |
-
}
|
169 |
-
|
170 |
-
const lastMigration = migrationFilesAttrs[migrationFilesAttrs.length - 1];
|
171 |
-
|
172 |
-
const migrationContent: MigrationFileContent = {
|
173 |
-
parent: lastMigration?.migrationName,
|
174 |
-
actions: migrationActionItems,
|
175 |
-
}
|
176 |
-
|
177 |
-
const num = String(Number(lastMigration?.num ?? '000000') + 1).padStart(6, '0');
|
178 |
-
|
179 |
-
const outputMigrationFile = isAbsolute(migrationFilePath)
|
180 |
-
? join(migrationFilePath, `${num}-${timestamp}.json`)
|
181 |
-
: join(projectPath, migrationFilePath, `${num}-${timestamp}.json`)
|
182 |
-
|
183 |
-
if (dryRun) {
|
184 |
-
console.info(`Creating migration file '${outputMigrationFile}'`);
|
185 |
-
console.info(migrationContent);
|
186 |
-
} else {
|
187 |
-
await writeFilePromisify(
|
188 |
-
outputMigrationFile,
|
189 |
-
JSON.stringify(migrationContent, null, 4),
|
190 |
-
'utf8',
|
191 |
-
);
|
192 |
-
}
|
193 |
-
}
|
194 |
-
|
195 |
-
export default generate;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
go-web-app-develop/app/scripts/translatte/commands/lint.test.ts
DELETED
@@ -1,75 +0,0 @@
|
|
1 |
-
import { expect } from 'vitest';
|
2 |
-
import { join } from 'path';
|
3 |
-
import { mkdirSync } from 'fs';
|
4 |
-
|
5 |
-
import { loginContent, registerContent } from '../mockData';
|
6 |
-
import { testWithTmpDir } from '../testHelpers';
|
7 |
-
import { writeFilePromisify } from '../utils';
|
8 |
-
import lint from './lint';
|
9 |
-
|
10 |
-
testWithTmpDir('test lint with duplicate file', async ({ tmpdir }) => {
|
11 |
-
mkdirSync(join(tmpdir, 'i18n'));
|
12 |
-
|
13 |
-
const writes = [
|
14 |
-
{ name: 'login.i18n.json', content: loginContent },
|
15 |
-
{ name: 'register.i18n.json', content: registerContent },
|
16 |
-
{ name: 'register-form.i18n.json', content: registerContent },
|
17 |
-
].map(({ name, content }) => writeFilePromisify(
|
18 |
-
join(tmpdir, 'i18n', name),
|
19 |
-
JSON.stringify(content, null, 4),
|
20 |
-
'utf8',
|
21 |
-
));
|
22 |
-
await Promise.all(writes);
|
23 |
-
|
24 |
-
await expect(
|
25 |
-
() => lint(tmpdir, ['**/*.i18n.json'], false)
|
26 |
-
).rejects.toThrow('Found 12 duplicated strings.');
|
27 |
-
});
|
28 |
-
|
29 |
-
testWithTmpDir('test lint with duplicate string and same text', async ({ tmpdir }) => {
|
30 |
-
mkdirSync(join(tmpdir, 'i18n'));
|
31 |
-
|
32 |
-
const writes = [
|
33 |
-
{ name: 'login.i18n.json', content: loginContent },
|
34 |
-
{ name: 'register.i18n.json', content: registerContent },
|
35 |
-
{ name: 'register-form.i18n.json', content: {
|
36 |
-
namespace: 'register',
|
37 |
-
strings: {
|
38 |
-
firstNameLabel: 'First Name',
|
39 |
-
},
|
40 |
-
} },
|
41 |
-
].map(({ name, content }) => writeFilePromisify(
|
42 |
-
join(tmpdir, 'i18n', name),
|
43 |
-
JSON.stringify(content, null, 4),
|
44 |
-
'utf8',
|
45 |
-
));
|
46 |
-
await Promise.all(writes);
|
47 |
-
|
48 |
-
await expect(
|
49 |
-
() => lint(tmpdir, ['**/*.i18n.json'], false)
|
50 |
-
).rejects.toThrow('Found 2 duplicated strings.');
|
51 |
-
});
|
52 |
-
|
53 |
-
testWithTmpDir('test lint with duplicate string and different text', async ({ tmpdir }) => {
|
54 |
-
mkdirSync(join(tmpdir, 'i18n'));
|
55 |
-
|
56 |
-
const writes = [
|
57 |
-
{ name: 'login.i18n.json', content: loginContent },
|
58 |
-
{ name: 'register.i18n.json', content: registerContent },
|
59 |
-
{ name: 'register-form.i18n.json', content: {
|
60 |
-
namespace: 'register',
|
61 |
-
strings: {
|
62 |
-
firstNameLabel: 'First Name*',
|
63 |
-
},
|
64 |
-
} },
|
65 |
-
].map(({ name, content }) => writeFilePromisify(
|
66 |
-
join(tmpdir, 'i18n', name),
|
67 |
-
JSON.stringify(content, null, 4),
|
68 |
-
'utf8',
|
69 |
-
));
|
70 |
-
await Promise.all(writes);
|
71 |
-
|
72 |
-
await expect(
|
73 |
-
() => lint(tmpdir, ['**/*.i18n.json'], false)
|
74 |
-
).rejects.toThrow('Found 2 duplicated strings.');
|
75 |
-
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
go-web-app-develop/app/scripts/translatte/commands/lint.ts
DELETED
@@ -1,106 +0,0 @@
|
|
1 |
-
import { listToMap } from '@togglecorp/fujs';
|
2 |
-
|
3 |
-
import {
|
4 |
-
getDuplicateItems,
|
5 |
-
getTranslationFileNames,
|
6 |
-
readTranslations,
|
7 |
-
writeFilePromisify,
|
8 |
-
} from '../utils';
|
9 |
-
|
10 |
-
function lowercaseFirstChar(chars: string) {
|
11 |
-
return chars.charAt(0).toLowerCase() + chars.slice(1);
|
12 |
-
}
|
13 |
-
|
14 |
-
async function lint(
|
15 |
-
projectPath: string,
|
16 |
-
translationFileName: string[],
|
17 |
-
fix: boolean | undefined,
|
18 |
-
) {
|
19 |
-
const fileNames = await getTranslationFileNames(projectPath, translationFileName);
|
20 |
-
const { translations, filesContents } = await readTranslations(fileNames);
|
21 |
-
|
22 |
-
const namespaces = new Set(translations.map((item) => item.namespace));
|
23 |
-
|
24 |
-
console.info(`Found ${namespaces.size} namespaces.`);
|
25 |
-
console.info(`Found ${translations.length} strings.`);
|
26 |
-
|
27 |
-
const duplicates = getDuplicateItems(
|
28 |
-
translations,
|
29 |
-
(string) => `${string.namespace}:${string.key}`,
|
30 |
-
);
|
31 |
-
|
32 |
-
if (duplicates.length > 0) {
|
33 |
-
console.info(JSON.stringify(duplicates, null, 2));
|
34 |
-
throw `Found ${duplicates.length} duplicated strings.`;
|
35 |
-
}
|
36 |
-
|
37 |
-
// FIXME: We should get these custom rules from config file later
|
38 |
-
const customRules: {
|
39 |
-
location: string,
|
40 |
-
namespace: ((match: RegExpMatchArray) => string) | string;
|
41 |
-
}[] = [
|
42 |
-
{ location: '.*/app/src/views/(\\w+)/(?:.*/)?i18n.json$', namespace: (match) => lowercaseFirstChar(match[1]) },
|
43 |
-
{ location: '.*/app/src/components/domain/(\\w+)/(?:.*/)?i18n.json$', namespace: (match) => lowercaseFirstChar(match[1]) },
|
44 |
-
{ location: '.*/app/src/.*/i18n.json$', namespace: 'common' },
|
45 |
-
{ location: '.*/packages/ui/src/.*/i18n.json$', namespace: 'common' },
|
46 |
-
];
|
47 |
-
|
48 |
-
const namespaceErrors: {
|
49 |
-
fileName: string,
|
50 |
-
expectedNamespace: string,
|
51 |
-
receivedNamespace: string,
|
52 |
-
}[] = [];
|
53 |
-
for (const item of filesContents) {
|
54 |
-
const { file: fileName, content: { namespace } } = item;
|
55 |
-
for (const rule of customRules) {
|
56 |
-
const match = fileName.match(new RegExp(rule.location));
|
57 |
-
if (match) {
|
58 |
-
const correctNamespace = typeof rule.namespace === 'string'
|
59 |
-
? rule.namespace
|
60 |
-
: rule.namespace(match);
|
61 |
-
if (correctNamespace !== namespace) {
|
62 |
-
namespaceErrors.push({
|
63 |
-
fileName,
|
64 |
-
expectedNamespace: correctNamespace,
|
65 |
-
receivedNamespace: namespace,
|
66 |
-
})
|
67 |
-
}
|
68 |
-
break;
|
69 |
-
}
|
70 |
-
};
|
71 |
-
};
|
72 |
-
|
73 |
-
if (namespaceErrors.length > 0) {
|
74 |
-
if (fix) {
|
75 |
-
const metadataMapping = listToMap(
|
76 |
-
filesContents,
|
77 |
-
(fileContents) => fileContents.file,
|
78 |
-
(fileContents) => fileContents.content,
|
79 |
-
);
|
80 |
-
const updates = namespaceErrors.map((namespaceError) => {
|
81 |
-
const content = metadataMapping[namespaceError.fileName];
|
82 |
-
const updatedContent = {
|
83 |
-
...content,
|
84 |
-
namespace: namespaceError.expectedNamespace,
|
85 |
-
}
|
86 |
-
return writeFilePromisify(
|
87 |
-
namespaceError.fileName,
|
88 |
-
JSON.stringify(updatedContent, null, 4),
|
89 |
-
'utf8',
|
90 |
-
);
|
91 |
-
});
|
92 |
-
await Promise.all(updates);
|
93 |
-
console.info(`Fixed namespace in ${namespaceErrors.length} files`);
|
94 |
-
} else {
|
95 |
-
console.info(JSON.stringify(namespaceErrors, null, 2));
|
96 |
-
throw `Found ${namespaceErrors.length} issues with namespaces.`;
|
97 |
-
}
|
98 |
-
}
|
99 |
-
|
100 |
-
// TODO: Throw error
|
101 |
-
// - if the naming of migration files is not correct
|
102 |
-
// - if the parent field is not correct
|
103 |
-
// - if we have duplicates
|
104 |
-
}
|
105 |
-
|
106 |
-
export default lint;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
go-web-app-develop/app/scripts/translatte/commands/listMigrations.test.ts
DELETED
@@ -1,48 +0,0 @@
|
|
1 |
-
import { expect } from 'vitest';
|
2 |
-
import { join } from 'path';
|
3 |
-
import { mkdirSync } from 'fs';
|
4 |
-
|
5 |
-
import { testWithTmpDir } from '../testHelpers';
|
6 |
-
import { writeFilePromisify, getMigrationFilesAttrs } from '../utils';
|
7 |
-
import {
|
8 |
-
migrationContent1,
|
9 |
-
migrationContent2,
|
10 |
-
migrationContent3,
|
11 |
-
migrationContent4,
|
12 |
-
migrationContent5,
|
13 |
-
} from '../mockData';
|
14 |
-
|
15 |
-
testWithTmpDir('test listMigrations', async ({ tmpdir }) => {
|
16 |
-
mkdirSync(join(tmpdir, 'migrations'));
|
17 |
-
|
18 |
-
const writes = [
|
19 |
-
{ name: '001-1000000000000.json', content: migrationContent1 },
|
20 |
-
{ name: '002-1000000000000.json', content: migrationContent2 },
|
21 |
-
{ name: '003-1000000000000.json', content: migrationContent3 },
|
22 |
-
{ name: '004-1000000000000.json', content: migrationContent4 },
|
23 |
-
{ name: '005-1000000000000.json', content: migrationContent5 },
|
24 |
-
|
25 |
-
{ name: 'xyz-1000000000000.json', content: migrationContent5 },
|
26 |
-
{ name: '006-abcdefghijklm.json', content: migrationContent5 },
|
27 |
-
{ name: '005-1000000000000', content: migrationContent5 },
|
28 |
-
{ name: 'migration-6.json', content: migrationContent5 },
|
29 |
-
].map(({ name, content }) => writeFilePromisify(
|
30 |
-
join(tmpdir, 'migrations', name),
|
31 |
-
JSON.stringify(content, null, 4),
|
32 |
-
'utf8',
|
33 |
-
));
|
34 |
-
await Promise.all(writes);
|
35 |
-
|
36 |
-
expect(
|
37 |
-
(await getMigrationFilesAttrs(
|
38 |
-
tmpdir,
|
39 |
-
'migrations',
|
40 |
-
)).map((item) => ({ ...item, fileName: undefined })),
|
41 |
-
).toEqual([
|
42 |
-
{ migrationName: '001-1000000000000.json', num: '001', timestamp: '1000000000000' },
|
43 |
-
{ migrationName: '002-1000000000000.json', num: '002', timestamp: '1000000000000' },
|
44 |
-
{ migrationName: '003-1000000000000.json', num: '003', timestamp: '1000000000000' },
|
45 |
-
{ migrationName: '004-1000000000000.json', num: '004', timestamp: '1000000000000' },
|
46 |
-
{ migrationName: '005-1000000000000.json', num: '005', timestamp: '1000000000000' },
|
47 |
-
]);
|
48 |
-
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
go-web-app-develop/app/scripts/translatte/commands/listMigrations.ts
DELETED
@@ -1,11 +0,0 @@
|
|
1 |
-
import { getMigrationFilesAttrs } from '../utils';
|
2 |
-
|
3 |
-
async function listMigrations(projectPath: string, path: string) {
|
4 |
-
const migrationFileAttrs = await getMigrationFilesAttrs(projectPath, path);
|
5 |
-
console.info(`Found ${migrationFileAttrs.length} migration files.`);
|
6 |
-
if (migrationFileAttrs.length > 0) {
|
7 |
-
console.info(migrationFileAttrs);
|
8 |
-
}
|
9 |
-
}
|
10 |
-
|
11 |
-
export default listMigrations;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
go-web-app-develop/app/scripts/translatte/commands/mergeMigrations.test.ts
DELETED
@@ -1,371 +0,0 @@
|
|
1 |
-
import { expect, test } from 'vitest';
|
2 |
-
import { existsSync } from 'fs';
|
3 |
-
import { join } from 'path';
|
4 |
-
|
5 |
-
import mergeMigrations, { merge } from './mergeMigrations';
|
6 |
-
import { testWithTmpDir } from '../testHelpers';
|
7 |
-
import { writeFilePromisify, readMigrations } from '../utils';
|
8 |
-
import {
|
9 |
-
migrationContent1,
|
10 |
-
migrationContent2,
|
11 |
-
migrationContent3,
|
12 |
-
migrationContent4,
|
13 |
-
migrationContent5,
|
14 |
-
} from '../mockData';
|
15 |
-
|
16 |
-
test('Test merge migrations 1-5', () => {
|
17 |
-
expect(merge([
|
18 |
-
migrationContent1,
|
19 |
-
migrationContent2,
|
20 |
-
migrationContent3,
|
21 |
-
migrationContent4,
|
22 |
-
migrationContent5,
|
23 |
-
])).toEqual([
|
24 |
-
{
|
25 |
-
"action": "add",
|
26 |
-
"namespace": "login",
|
27 |
-
"key": "emailLabel",
|
28 |
-
"value": "Email/Username"
|
29 |
-
},
|
30 |
-
{
|
31 |
-
"action": "add",
|
32 |
-
"namespace": "login",
|
33 |
-
"key": "emailPlaceholder",
|
34 |
-
"value": "Email/Username*"
|
35 |
-
},
|
36 |
-
{
|
37 |
-
"action": "add",
|
38 |
-
"namespace": "login",
|
39 |
-
"key": "passwordLabel",
|
40 |
-
"value": "Password"
|
41 |
-
},
|
42 |
-
{
|
43 |
-
"action": "add",
|
44 |
-
"namespace": "login",
|
45 |
-
"key": "loginButton",
|
46 |
-
"value": "Login"
|
47 |
-
},
|
48 |
-
{
|
49 |
-
"action": "add",
|
50 |
-
"namespace": "register",
|
51 |
-
"key": "firstNameLabel",
|
52 |
-
"value": "First Name"
|
53 |
-
},
|
54 |
-
{
|
55 |
-
"action": "add",
|
56 |
-
"namespace": "register",
|
57 |
-
"key": "lastNameLabel",
|
58 |
-
"value": "Last Name"
|
59 |
-
},
|
60 |
-
{
|
61 |
-
"action": "add",
|
62 |
-
"namespace": "register",
|
63 |
-
"key": "emailLabel",
|
64 |
-
"value": "Email"
|
65 |
-
},
|
66 |
-
{
|
67 |
-
"action": "add",
|
68 |
-
"namespace": "register",
|
69 |
-
"key": "passwordLabel",
|
70 |
-
"value": "Password"
|
71 |
-
},
|
72 |
-
{
|
73 |
-
"action": "add",
|
74 |
-
"namespace": "register",
|
75 |
-
"key": "confirmPasswordLabel",
|
76 |
-
"value": "Confirm Password"
|
77 |
-
},
|
78 |
-
{
|
79 |
-
"action": "add",
|
80 |
-
"namespace": "login",
|
81 |
-
"key": "header",
|
82 |
-
"value": "If you are staff, member or volunteer of the Red Cross Red Crescent Movement (National Societies, the IFRC and the ICRC) login with you email and password."
|
83 |
-
},
|
84 |
-
{
|
85 |
-
"action": "add",
|
86 |
-
"namespace": "register",
|
87 |
-
"key": "registerButton",
|
88 |
-
"value": "Register"
|
89 |
-
},
|
90 |
-
]);
|
91 |
-
});
|
92 |
-
|
93 |
-
test('Test merge migrations 2-5', () => {
|
94 |
-
expect(merge([
|
95 |
-
migrationContent2,
|
96 |
-
migrationContent3,
|
97 |
-
migrationContent4,
|
98 |
-
migrationContent5,
|
99 |
-
])).toEqual([
|
100 |
-
{
|
101 |
-
"action": "update",
|
102 |
-
"namespace": "login",
|
103 |
-
"key": "emailLabel",
|
104 |
-
"newValue": "Email/Username"
|
105 |
-
},
|
106 |
-
{
|
107 |
-
"action": "update",
|
108 |
-
"namespace": "login",
|
109 |
-
"key": "passwordLabel",
|
110 |
-
"newValue": "Password"
|
111 |
-
},
|
112 |
-
{
|
113 |
-
"action": "update",
|
114 |
-
"namespace": "register",
|
115 |
-
"key": "firstNameLabel",
|
116 |
-
"newValue": "First Name"
|
117 |
-
},
|
118 |
-
{
|
119 |
-
"action": "update",
|
120 |
-
"namespace": "register",
|
121 |
-
"key": "lastNameLabel",
|
122 |
-
"newValue": "Last Name"
|
123 |
-
},
|
124 |
-
{
|
125 |
-
"action": "update",
|
126 |
-
"namespace": "register",
|
127 |
-
"key": "emailLabel",
|
128 |
-
"newValue": "Email"
|
129 |
-
},
|
130 |
-
{
|
131 |
-
"action": "update",
|
132 |
-
"namespace": "register",
|
133 |
-
"key": "passwordLabel",
|
134 |
-
"newValue": "Password"
|
135 |
-
},
|
136 |
-
{
|
137 |
-
"action": "update",
|
138 |
-
"namespace": "register",
|
139 |
-
"key": "confirmPasswordLabel",
|
140 |
-
"newValue": "Confirm Password"
|
141 |
-
},
|
142 |
-
{
|
143 |
-
"action": "update",
|
144 |
-
"key": "signUpButton",
|
145 |
-
"namespace": "register",
|
146 |
-
"newKey": "registerButton",
|
147 |
-
"newNamespace": undefined,
|
148 |
-
"newValue": "Register",
|
149 |
-
},
|
150 |
-
{
|
151 |
-
"action": "add",
|
152 |
-
"namespace": "login",
|
153 |
-
"key": "header",
|
154 |
-
"value": "If you are staff, member or volunteer of the Red Cross Red Crescent Movement (National Societies, the IFRC and the ICRC) login with you email and password."
|
155 |
-
},
|
156 |
-
{
|
157 |
-
"action": "remove",
|
158 |
-
"namespace": "home",
|
159 |
-
"key": "header"
|
160 |
-
},
|
161 |
-
{
|
162 |
-
"action": "remove",
|
163 |
-
"namespace": "home",
|
164 |
-
"key": "subHeader"
|
165 |
-
},
|
166 |
-
]);
|
167 |
-
});
|
168 |
-
|
169 |
-
test('Test merge migrations 3-5', () => {
|
170 |
-
expect(merge([
|
171 |
-
migrationContent3,
|
172 |
-
migrationContent4,
|
173 |
-
migrationContent5,
|
174 |
-
])).toEqual([
|
175 |
-
{
|
176 |
-
"action": "add",
|
177 |
-
"namespace": "login",
|
178 |
-
"key": "header",
|
179 |
-
"value": "If you are staff, member or volunteer of the Red Cross Red Crescent Movement (National Societies, the IFRC and the ICRC) login with you email and password."
|
180 |
-
},
|
181 |
-
{
|
182 |
-
"action": "update",
|
183 |
-
"namespace": "register",
|
184 |
-
"key": "signUpButton",
|
185 |
-
"newKey": "registerButton"
|
186 |
-
},
|
187 |
-
{
|
188 |
-
"action": "remove",
|
189 |
-
"namespace": "home",
|
190 |
-
"key": "header"
|
191 |
-
},
|
192 |
-
{
|
193 |
-
"action": "remove",
|
194 |
-
"namespace": "home",
|
195 |
-
"key": "subHeader"
|
196 |
-
},
|
197 |
-
]);
|
198 |
-
});
|
199 |
-
|
200 |
-
test('Test merge migrations 4-5', () => {
|
201 |
-
expect(merge([
|
202 |
-
migrationContent4,
|
203 |
-
migrationContent5,
|
204 |
-
])).toEqual([
|
205 |
-
{
|
206 |
-
"action": "remove",
|
207 |
-
"namespace": "login",
|
208 |
-
"key": "header"
|
209 |
-
},
|
210 |
-
{
|
211 |
-
"action": "update",
|
212 |
-
"namespace": "register",
|
213 |
-
"key": "header",
|
214 |
-
"newNamespace": "login"
|
215 |
-
},
|
216 |
-
{
|
217 |
-
"action": "update",
|
218 |
-
"namespace": "register",
|
219 |
-
"key": "signUpButton",
|
220 |
-
"newKey": "registerButton"
|
221 |
-
},
|
222 |
-
{
|
223 |
-
"action": "remove",
|
224 |
-
"namespace": "home",
|
225 |
-
"key": "header"
|
226 |
-
},
|
227 |
-
{
|
228 |
-
"action": "remove",
|
229 |
-
"namespace": "home",
|
230 |
-
"key": "subHeader"
|
231 |
-
},
|
232 |
-
]);
|
233 |
-
});
|
234 |
-
|
235 |
-
test('Test merge migrations 5-5', () => {
|
236 |
-
expect(merge([
|
237 |
-
migrationContent5,
|
238 |
-
])).toEqual(migrationContent5.actions)
|
239 |
-
})
|
240 |
-
|
241 |
-
testWithTmpDir('test mergeMigrations 1-5', async ({ tmpdir }) => {
|
242 |
-
const writes = [
|
243 |
-
{ name: '000001-1000000000000.json', content: migrationContent1 },
|
244 |
-
{ name: '000002-1000000000000.json', content: migrationContent2 },
|
245 |
-
{ name: '000003-1000000000000.json', content: migrationContent3 },
|
246 |
-
{ name: '000004-1000000000000.json', content: migrationContent4 },
|
247 |
-
{ name: '000005-1000000000000.json', content: migrationContent5 },
|
248 |
-
].map(({ name, content }) => writeFilePromisify(
|
249 |
-
join(tmpdir, name),
|
250 |
-
JSON.stringify(content, null, 4),
|
251 |
-
'utf8',
|
252 |
-
));
|
253 |
-
await Promise.all(writes);
|
254 |
-
|
255 |
-
await mergeMigrations(
|
256 |
-
tmpdir,
|
257 |
-
'.',
|
258 |
-
'000001-1000000000000.json',
|
259 |
-
'000005-1000000000000.json',
|
260 |
-
false,
|
261 |
-
);
|
262 |
-
|
263 |
-
expect(existsSync(join(tmpdir, '000001-1000000000000.json'))).toBeFalsy();
|
264 |
-
expect(existsSync(join(tmpdir, '000002-1000000000000.json'))).toBeFalsy();
|
265 |
-
expect(existsSync(join(tmpdir, '000003-1000000000000.json'))).toBeFalsy();
|
266 |
-
expect(existsSync(join(tmpdir, '000004-1000000000000.json'))).toBeFalsy();
|
267 |
-
expect(existsSync(join(tmpdir, '000005-1000000000000.json'))).toBeTruthy();
|
268 |
-
|
269 |
-
const generatedFiles = await readMigrations([join(tmpdir, '000005-1000000000000.json')]);
|
270 |
-
const generatedFile = generatedFiles[0];
|
271 |
-
expect(generatedFile.content.parent).toBe(undefined);
|
272 |
-
});
|
273 |
-
|
274 |
-
testWithTmpDir('test mergeMigrations 2-5', async ({ tmpdir }) => {
|
275 |
-
const writes = [
|
276 |
-
{ name: '000001-1000000000000.json', content: migrationContent1 },
|
277 |
-
{ name: '000002-1000000000000.json', content: migrationContent2 },
|
278 |
-
{ name: '000003-1000000000000.json', content: migrationContent3 },
|
279 |
-
{ name: '000004-1000000000000.json', content: migrationContent4 },
|
280 |
-
{ name: '000005-1000000000000.json', content: migrationContent5 },
|
281 |
-
].map(({ name, content }) => writeFilePromisify(
|
282 |
-
join(tmpdir, name),
|
283 |
-
JSON.stringify(content, null, 4),
|
284 |
-
'utf8',
|
285 |
-
));
|
286 |
-
await Promise.all(writes);
|
287 |
-
|
288 |
-
await mergeMigrations(
|
289 |
-
tmpdir,
|
290 |
-
'.',
|
291 |
-
'000002-1000000000000.json',
|
292 |
-
'000005-1000000000000.json',
|
293 |
-
false,
|
294 |
-
);
|
295 |
-
|
296 |
-
expect(existsSync(join(tmpdir, '000001-1000000000000.json'))).toBeTruthy();
|
297 |
-
expect(existsSync(join(tmpdir, '000002-1000000000000.json'))).toBeFalsy();
|
298 |
-
expect(existsSync(join(tmpdir, '000003-1000000000000.json'))).toBeFalsy();
|
299 |
-
expect(existsSync(join(tmpdir, '000004-1000000000000.json'))).toBeFalsy();
|
300 |
-
expect(existsSync(join(tmpdir, '000005-1000000000000.json'))).toBeTruthy();
|
301 |
-
|
302 |
-
const generatedFiles = await readMigrations([join(tmpdir, '000005-1000000000000.json')]);
|
303 |
-
const generatedFile = generatedFiles[0];
|
304 |
-
expect(generatedFile.content.parent).toBe('000001-1000000000000');
|
305 |
-
});
|
306 |
-
|
307 |
-
testWithTmpDir('test mergeMigrations 3-5', async ({ tmpdir }) => {
|
308 |
-
const writes = [
|
309 |
-
{ name: '000001-1000000000000.json', content: migrationContent1 },
|
310 |
-
{ name: '000002-1000000000000.json', content: migrationContent2 },
|
311 |
-
{ name: '000003-1000000000000.json', content: migrationContent3 },
|
312 |
-
{ name: '000004-1000000000000.json', content: migrationContent4 },
|
313 |
-
{ name: '000005-1000000000000.json', content: migrationContent5 },
|
314 |
-
].map(({ name, content }) => writeFilePromisify(
|
315 |
-
join(tmpdir, name),
|
316 |
-
JSON.stringify(content, null, 4),
|
317 |
-
'utf8',
|
318 |
-
));
|
319 |
-
await Promise.all(writes);
|
320 |
-
|
321 |
-
await mergeMigrations(
|
322 |
-
tmpdir,
|
323 |
-
'.',
|
324 |
-
'000003-1000000000000.json',
|
325 |
-
'000005-1000000000000.json',
|
326 |
-
false,
|
327 |
-
);
|
328 |
-
|
329 |
-
expect(existsSync(join(tmpdir, '000001-1000000000000.json'))).toBeTruthy();
|
330 |
-
expect(existsSync(join(tmpdir, '000002-1000000000000.json'))).toBeTruthy();
|
331 |
-
expect(existsSync(join(tmpdir, '000003-1000000000000.json'))).toBeFalsy();
|
332 |
-
expect(existsSync(join(tmpdir, '000004-1000000000000.json'))).toBeFalsy();
|
333 |
-
expect(existsSync(join(tmpdir, '000005-1000000000000.json'))).toBeTruthy();
|
334 |
-
|
335 |
-
const generatedFiles = await readMigrations([join(tmpdir, '000005-1000000000000.json')]);
|
336 |
-
const generatedFile = generatedFiles[0];
|
337 |
-
expect(generatedFile.content.parent).toBe('000002-1000000000000');
|
338 |
-
});
|
339 |
-
|
340 |
-
testWithTmpDir('test mergeMigrations 4-5', async ({ tmpdir }) => {
|
341 |
-
const writes = [
|
342 |
-
{ name: '000001-1000000000000.json', content: migrationContent1 },
|
343 |
-
{ name: '000002-1000000000000.json', content: migrationContent2 },
|
344 |
-
{ name: '000003-1000000000000.json', content: migrationContent3 },
|
345 |
-
{ name: '000004-1000000000000.json', content: migrationContent4 },
|
346 |
-
{ name: '000005-1000000000000.json', content: migrationContent5 },
|
347 |
-
].map(({ name, content }) => writeFilePromisify(
|
348 |
-
join(tmpdir, name),
|
349 |
-
JSON.stringify(content, null, 4),
|
350 |
-
'utf8',
|
351 |
-
));
|
352 |
-
await Promise.all(writes);
|
353 |
-
|
354 |
-
await mergeMigrations(
|
355 |
-
tmpdir,
|
356 |
-
'.',
|
357 |
-
'000004-1000000000000.json',
|
358 |
-
'000005-1000000000000.json',
|
359 |
-
false,
|
360 |
-
);
|
361 |
-
|
362 |
-
expect(existsSync(join(tmpdir, '000001-1000000000000.json'))).toBeTruthy();
|
363 |
-
expect(existsSync(join(tmpdir, '000002-1000000000000.json'))).toBeTruthy();
|
364 |
-
expect(existsSync(join(tmpdir, '000003-1000000000000.json'))).toBeTruthy();
|
365 |
-
expect(existsSync(join(tmpdir, '000004-1000000000000.json'))).toBeFalsy();
|
366 |
-
expect(existsSync(join(tmpdir, '000005-1000000000000.json'))).toBeTruthy();
|
367 |
-
|
368 |
-
const generatedFiles = await readMigrations([join(tmpdir, '000005-1000000000000.json')]);
|
369 |
-
const generatedFile = generatedFiles[0];
|
370 |
-
expect(generatedFile.content.parent).toBe('000003-1000000000000');
|
371 |
-
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
go-web-app-develop/app/scripts/translatte/commands/mergeMigrations.ts
DELETED
@@ -1,220 +0,0 @@
|
|
1 |
-
import { listToMap, isDefined } from '@togglecorp/fujs';
|
2 |
-
|
3 |
-
import { MigrationActionItem, MigrationFileContent } from '../types';
|
4 |
-
import {
|
5 |
-
concat,
|
6 |
-
removeUndefinedKeys,
|
7 |
-
getMigrationFilesAttrs,
|
8 |
-
readMigrations,
|
9 |
-
removeFiles,
|
10 |
-
writeFilePromisify
|
11 |
-
} from '../utils';
|
12 |
-
|
13 |
-
function getCanonicalKey(
|
14 |
-
item: MigrationActionItem,
|
15 |
-
opts: { useNewKey: boolean },
|
16 |
-
) {
|
17 |
-
if (opts.useNewKey && item.action === 'update') {
|
18 |
-
return concat(
|
19 |
-
item.newNamespace ?? item.namespace,
|
20 |
-
item.newKey ?? item.key,
|
21 |
-
);
|
22 |
-
}
|
23 |
-
return concat(
|
24 |
-
item.namespace,
|
25 |
-
item.key,
|
26 |
-
);
|
27 |
-
}
|
28 |
-
|
29 |
-
function mergeMigrationActionItems(
|
30 |
-
prevMigrationActionItems: MigrationActionItem[],
|
31 |
-
nextMigrationActionItems: MigrationActionItem[],
|
32 |
-
) {
|
33 |
-
interface PrevMappings {
|
34 |
-
[key: string]: MigrationActionItem,
|
35 |
-
}
|
36 |
-
|
37 |
-
const prevCanonicalKeyMappings: PrevMappings = listToMap(
|
38 |
-
prevMigrationActionItems,
|
39 |
-
(item) => getCanonicalKey(item, { useNewKey: true }),
|
40 |
-
(item) => item,
|
41 |
-
);
|
42 |
-
|
43 |
-
interface NextMappings {
|
44 |
-
[key: string]: MigrationActionItem | null,
|
45 |
-
}
|
46 |
-
|
47 |
-
const nextMappings = nextMigrationActionItems.reduce<NextMappings>(
|
48 |
-
(acc, nextMigrationActionItem) => {
|
49 |
-
const canonicalKey = getCanonicalKey(nextMigrationActionItem, { useNewKey: false })
|
50 |
-
|
51 |
-
const prevItemWithCanonicalKey = prevCanonicalKeyMappings[canonicalKey];
|
52 |
-
// const prevItemWithKey = prevKeyMappings[nextMigrationActionItem.key];
|
53 |
-
|
54 |
-
if (!prevItemWithCanonicalKey) {
|
55 |
-
return {
|
56 |
-
...acc,
|
57 |
-
[canonicalKey]: nextMigrationActionItem,
|
58 |
-
};
|
59 |
-
}
|
60 |
-
|
61 |
-
if (prevItemWithCanonicalKey.action === 'add' && nextMigrationActionItem.action === 'add') {
|
62 |
-
throw `Action 'add' already exists for '${canonicalKey}'`;
|
63 |
-
}
|
64 |
-
if (prevItemWithCanonicalKey.action === 'add' && nextMigrationActionItem.action === 'remove') {
|
65 |
-
return {
|
66 |
-
...acc,
|
67 |
-
[canonicalKey]: null,
|
68 |
-
};
|
69 |
-
}
|
70 |
-
if (prevItemWithCanonicalKey.action === 'add' && nextMigrationActionItem.action === 'update') {
|
71 |
-
const newKey = nextMigrationActionItem.newKey
|
72 |
-
?? prevItemWithCanonicalKey.key;
|
73 |
-
const newNamespace = nextMigrationActionItem.newNamespace
|
74 |
-
?? prevItemWithCanonicalKey.namespace;
|
75 |
-
|
76 |
-
const newMigrationItem = removeUndefinedKeys<MigrationActionItem>({
|
77 |
-
action: 'add',
|
78 |
-
namespace: newNamespace,
|
79 |
-
key: newKey,
|
80 |
-
value: nextMigrationActionItem.newValue
|
81 |
-
?? prevItemWithCanonicalKey.value,
|
82 |
-
});
|
83 |
-
|
84 |
-
const newCanonicalKey = getCanonicalKey(newMigrationItem, { useNewKey: true });
|
85 |
-
if (acc[newCanonicalKey] !== undefined && acc[newCanonicalKey] !== null) {
|
86 |
-
throw `Action 'update' cannot be applied to '${newCanonicalKey}' as the key already exists`;
|
87 |
-
}
|
88 |
-
|
89 |
-
return {
|
90 |
-
...acc,
|
91 |
-
// Setting null so that we remove them on the mappings.
|
92 |
-
// No need to set null, if we have already overridden with other value
|
93 |
-
[canonicalKey]: acc[canonicalKey] === undefined || acc[canonicalKey] === null
|
94 |
-
? null
|
95 |
-
: acc[canonicalKey],
|
96 |
-
[newCanonicalKey]: newMigrationItem,
|
97 |
-
}
|
98 |
-
}
|
99 |
-
if (prevItemWithCanonicalKey.action === 'remove' && nextMigrationActionItem.action === 'add') {
|
100 |
-
return {
|
101 |
-
...acc,
|
102 |
-
[canonicalKey]: removeUndefinedKeys<MigrationActionItem>({
|
103 |
-
action: 'update',
|
104 |
-
namespace: prevItemWithCanonicalKey.namespace,
|
105 |
-
key: prevItemWithCanonicalKey.key,
|
106 |
-
newValue: nextMigrationActionItem.value,
|
107 |
-
})
|
108 |
-
};
|
109 |
-
}
|
110 |
-
if (prevItemWithCanonicalKey.action === 'remove' && nextMigrationActionItem.action === 'remove') {
|
111 |
-
// pass
|
112 |
-
return acc;
|
113 |
-
}
|
114 |
-
if (prevItemWithCanonicalKey.action === 'remove' && nextMigrationActionItem.action === 'update') {
|
115 |
-
throw `Action 'update' cannot be applied to '${canonicalKey}' after action 'remove'`;
|
116 |
-
}
|
117 |
-
if (prevItemWithCanonicalKey.action === 'update' && nextMigrationActionItem.action === 'add') {
|
118 |
-
throw `Action 'add' cannot be applied to '${canonicalKey}' after action 'update'`;
|
119 |
-
}
|
120 |
-
if (prevItemWithCanonicalKey.action === 'update' && nextMigrationActionItem.action === 'update') {
|
121 |
-
return {
|
122 |
-
...acc,
|
123 |
-
[canonicalKey]: removeUndefinedKeys<MigrationActionItem>({
|
124 |
-
action: 'update',
|
125 |
-
namespace: prevItemWithCanonicalKey.namespace,
|
126 |
-
key: prevItemWithCanonicalKey.key,
|
127 |
-
newNamespace: nextMigrationActionItem.newNamespace ?? prevItemWithCanonicalKey.newNamespace,
|
128 |
-
newKey: nextMigrationActionItem.newKey ?? prevItemWithCanonicalKey.newKey,
|
129 |
-
newValue: nextMigrationActionItem.newValue ?? prevItemWithCanonicalKey.newValue,
|
130 |
-
}),
|
131 |
-
};
|
132 |
-
}
|
133 |
-
if (prevItemWithCanonicalKey.action === 'update' && nextMigrationActionItem.action === 'remove') {
|
134 |
-
return {
|
135 |
-
...acc,
|
136 |
-
[canonicalKey]: removeUndefinedKeys<MigrationActionItem>({
|
137 |
-
action: 'remove',
|
138 |
-
namespace: prevItemWithCanonicalKey.namespace,
|
139 |
-
key: prevItemWithCanonicalKey.key,
|
140 |
-
}),
|
141 |
-
};
|
142 |
-
}
|
143 |
-
return acc;
|
144 |
-
},
|
145 |
-
{},
|
146 |
-
);
|
147 |
-
|
148 |
-
const finalMappings = {
|
149 |
-
...prevCanonicalKeyMappings,
|
150 |
-
...nextMappings,
|
151 |
-
};
|
152 |
-
|
153 |
-
return Object.values(finalMappings).filter(isDefined);
|
154 |
-
}
|
155 |
-
|
156 |
-
export function merge(migrationFileContents: MigrationFileContent[]) {
|
157 |
-
const migrationActionItems = migrationFileContents.reduce<MigrationActionItem[]>(
|
158 |
-
(acc, migrationActionItem) => {
|
159 |
-
const newMigrationItems = mergeMigrationActionItems(acc, migrationActionItem.actions)
|
160 |
-
return newMigrationItems;
|
161 |
-
},
|
162 |
-
[],
|
163 |
-
);
|
164 |
-
|
165 |
-
return migrationActionItems;
|
166 |
-
}
|
167 |
-
|
168 |
-
async function mergeMigrations(
|
169 |
-
projectPath: string,
|
170 |
-
path: string,
|
171 |
-
from: string,
|
172 |
-
to: string,
|
173 |
-
dryRun: boolean | undefined,
|
174 |
-
) {
|
175 |
-
const migrationFilesAttrs = await getMigrationFilesAttrs(projectPath, path);
|
176 |
-
const selectedMigrationFilesAttrs = migrationFilesAttrs.filter(
|
177 |
-
(item) => (item.migrationName >= from && item.migrationName <= to)
|
178 |
-
);
|
179 |
-
console.info(`Found ${selectedMigrationFilesAttrs.length} migration files`);
|
180 |
-
|
181 |
-
if (selectedMigrationFilesAttrs.length <= 1) {
|
182 |
-
throw 'There should be atleast 2 migration files';
|
183 |
-
}
|
184 |
-
const selectedMigrations = await readMigrations(
|
185 |
-
selectedMigrationFilesAttrs.map((migration) => migration.fileName),
|
186 |
-
);
|
187 |
-
|
188 |
-
const firstMigration= selectedMigrations[0];
|
189 |
-
const lastMigration = selectedMigrations[selectedMigrations.length - 1];
|
190 |
-
|
191 |
-
const selectedMigrationsFileNames = selectedMigrationFilesAttrs.map((migration) => migration.fileName);
|
192 |
-
|
193 |
-
const mergedMigrationContent = {
|
194 |
-
actions: merge(selectedMigrations.map((migration) => migration.content)),
|
195 |
-
parent: firstMigration.content.parent,
|
196 |
-
};
|
197 |
-
|
198 |
-
if (dryRun) {
|
199 |
-
console.info('Deleting the following migration files');
|
200 |
-
console.info(selectedMigrationsFileNames);
|
201 |
-
} else {
|
202 |
-
await removeFiles(
|
203 |
-
selectedMigrationsFileNames,
|
204 |
-
);
|
205 |
-
}
|
206 |
-
|
207 |
-
const newFileName = lastMigration.file;
|
208 |
-
if (dryRun) {
|
209 |
-
console.info(`Creating migration file '${newFileName}'`);
|
210 |
-
console.info(mergedMigrationContent);
|
211 |
-
} else {
|
212 |
-
await writeFilePromisify(
|
213 |
-
newFileName,
|
214 |
-
JSON.stringify(mergedMigrationContent, null, 4),
|
215 |
-
'utf8',
|
216 |
-
);
|
217 |
-
}
|
218 |
-
}
|
219 |
-
|
220 |
-
export default mergeMigrations;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|