Compare commits

...

6 Commits

164 changed files with 791 additions and 24 deletions

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 26.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 13 13" style="enable-background:new 0 0 13 13;" xml:space="preserve">
<path d="M9.5,6H7V3.5C7,3.22,6.78,3,6.5,3S6,3.22,6,3.5V6H3.5C3.22,6,3,6.22,3,6.5S3.22,7,3.5,7H6v2.5C6,9.78,6.22,10,6.5,10
S7,9.78,7,9.5V7h2.5C9.78,7,10,6.78,10,6.5S9.78,6,9.5,6z"/>
</svg>

After

Width:  |  Height:  |  Size: 541 B

View File

@ -0,0 +1,16 @@
import { FlorisPlot } from './types';
export const FLORIS_ENDPOINTS = {
getWindmillData: 'api/floris/get_windmill_data',
};
export const FLORIS_PLOTS: Record<string, FlorisPlot> = {
horizontalPlane: {
name: 'horizontal_plane',
label: 'Horizontal Plane',
},
verticalPlane: {
name: 'vertical_plane',
label: 'Vertical Plane',
},
};

View File

@ -0,0 +1,2 @@
export * from './constants';
export * from './service';

View File

@ -0,0 +1,14 @@
import { api } from '@api/api';
import { FlorisFormValues } from '@components/ux/floris-form/types';
import { FLORIS_ENDPOINTS } from './constants';
import { WindmillData } from './types';
import { getWindmillDataRequestParams } from './utils';
export const getWindmillData = (formValues: Partial<FlorisFormValues>) => {
const { park } = formValues;
const params = getWindmillDataRequestParams(formValues);
const parkPath = park ? `/${park.id}/` : '';
const url = `${FLORIS_ENDPOINTS.getWindmillData}${parkPath}?${params}`;
return api.get<WindmillData>(url);
};

View File

@ -0,0 +1,9 @@
export type FlorisPlot = {
name: string;
label: string;
};
export type WindmillData = {
data: number[][];
fileName: Record<string, string>;
};

View File

@ -0,0 +1,29 @@
import { FlorisFormValues } from '@components/ux/floris-form/types';
import { FLORIS_PLOTS } from './constants';
export const getWindmillDataRequestParams = (
formValues: Partial<FlorisFormValues>,
) => {
let params = '';
if (formValues.turbines) {
const layoutX = formValues.turbines
?.map((row) => `layout_x=${row.x}`)
.join('&');
const layoutY = formValues.turbines
?.map((row) => `layout_y=${row.y}`)
.join('&');
const yawAngle = formValues.turbines
?.map((row) => `yaw_angle=${row.angle}`)
.join('&');
params += `${layoutX}&${layoutY}&${yawAngle}`;
}
const plots = Object.values(FLORIS_PLOTS)
.filter((_, i) => formValues.plots?.[i])
.map((p) => `plots=${p.name}`)
.join('&');
const dateStart = `date_start=${formValues.dateFrom?.substring(0, 10)}`;
const dateEnd = `date_end=${formValues.dateTo?.substring(0, 10)}`;
params += `&${plots}&${dateStart}&${dateEnd}`;
return params;
};

View File

@ -8,6 +8,7 @@ import {
TurbineTypePage,
TurbineTypesPage,
} from '@components/pages';
import { FlorisPage } from '@components/pages/floris-page/component';
import { ROUTES } from '@utils/route';
import React from 'react';
import { BrowserRouter, Navigate, Route, Routes } from 'react-router-dom';
@ -24,6 +25,7 @@ export function App() {
<Route path={ROUTES.turbineType.path} element={<TurbineTypePage />} />
<Route path={ROUTES.parks.path} element={<ParksPage />} />
<Route path={ROUTES.park.path} element={<ParkPage />} />
<Route path={ROUTES.floris.path} element={<FlorisPage />} />
</Route>
<Route
path="*"

View File

@ -0,0 +1,32 @@
import { WindmillData } from '@api/floris/types';
import { Heading } from '@components/ui';
import { FlorisForm, FlorisPlots, PowerSection } from '@components/ux';
import { useRoute } from '@utils/route';
import React, { useState } from 'react';
import styles from './styles.module.scss';
export function FlorisPage() {
const [data, setData] = useState<WindmillData>(null);
const [dateFrom, setDateFrom] = useState<string>(null);
const route = useRoute();
const handleFormSuccess = (data: WindmillData, dateFrom: string) => {
setData(data);
console.log(data);
setDateFrom(dateFrom);
};
return (
<div className={styles.page}>
<Heading tag="h1">{route.title}</Heading>
<FlorisForm onSuccess={handleFormSuccess} onFail={() => {}} />
{data && (
<>
<PowerSection power={data.data} dateFrom={dateFrom} />
<FlorisPlots filenames={data.fileName} />
</>
)}
</div>
);
}

View File

@ -0,0 +1,7 @@
.page {
display: grid;
padding: 40px 20px;
gap: 30px;
grid-template-columns: minmax(0, 1fr);
grid-template-rows: auto auto auto auto 1fr;
}

View File

@ -8,8 +8,8 @@ import { CheckboxGroupProps } from './types';
export function CheckboxGroup<T>({
name,
value,
items,
value = items.map(() => false),
onChange,
getItemKey,
getItemLabel,
@ -19,7 +19,7 @@ export function CheckboxGroup<T>({
const classNames = clsx(styles.checkBoxGroup, styles[scale]);
const handleChange = (index: number) => {
onChange(value.with(index, !value[index]));
onChange?.(value.with(index, !value[index]));
};
return (

View File

@ -2,9 +2,9 @@ import { Scale } from '../types';
export type CheckboxGroupProps<T> = {
name: string;
value: boolean[];
value?: boolean[];
items: T[];
onChange: (value: boolean[]) => void;
onChange?: (value: boolean[]) => void;
getItemKey: (item: T) => React.Key;
getItemLabel: (item: T) => string;
scale?: Scale;

View File

@ -0,0 +1,144 @@
import { FLORIS_PLOTS, getWindmillData } from '@api/floris';
import { getParks, Park } from '@api/wind';
import {
Autocomplete,
Button,
Checkbox,
CheckboxGroup,
DateInput,
Heading,
} from '@components/ui';
import { Controller, useForm } from '@utils/form';
import clsx from 'clsx';
import React, { useEffect, useState } from 'react';
import { FlorisTable } from '../floris-table/component';
import styles from './styles.module.scss';
import { FlorisFormProps, FlorisFormValues } from './types';
export function FlorisForm({
onSuccess,
onFail,
className,
...props
}: FlorisFormProps) {
const [pending, setPending] = useState<boolean>(false);
const [parks, setParks] = useState<Park[]>([]);
const [isManualEntry, setIsManualEntry] = useState<boolean>(false);
const { control, reset, getValues } = useForm<FlorisFormValues>({});
const fetchParks = async () => {
const res = await getParks();
setParks(res.data);
};
useEffect(() => {
fetchParks();
}, []);
const validate = (values: Partial<FlorisFormValues>) => {
console.log(values);
return true;
};
const handleSubmit = async (event: React.FormEvent) => {
event.preventDefault();
const values = getValues();
if (!validate(values)) {
return;
}
setPending(true);
const { data, error } = await getWindmillData(values);
if (data) {
onSuccess(data, values.dateFrom);
} else {
onFail(error.message);
}
setPending(false);
};
const handleResetButtonClick = () => {
reset({});
};
const handleManulEntryCheckboxChange = (
event: React.ChangeEvent<HTMLInputElement>,
) => {
const { checked } = event.target;
setIsManualEntry(checked);
if (checked) {
reset({ ...getValues(), park: undefined });
} else {
reset({ ...getValues(), turbines: undefined });
}
};
return (
<form
onSubmit={handleSubmit}
className={clsx(className, styles.form)}
{...props}
>
<Heading tag="h3">Turbines properties</Heading>
<div className={styles.content}>
<div className={styles.part}>
<div className={styles.dateRangeBox}>
<Controller
{...control('dateFrom')}
render={(params) => <DateInput placeholder="from" {...params} />}
/>
<Controller
{...control('dateTo')}
render={(params) => <DateInput placeholder="to" {...params} />}
/>
</div>
<Checkbox
label={{ text: 'Manual entry', position: 'right' }}
onChange={handleManulEntryCheckboxChange}
/>
{isManualEntry && (
<Controller
{...control('turbines')}
render={(params) => <FlorisTable {...params} />}
/>
)}
{!isManualEntry && (
<Controller
{...control('park')}
render={(params) => (
<Autocomplete
options={parks}
getOptionKey={(p) => p.id}
getOptionLabel={(p) => p.name}
{...params}
/>
)}
/>
)}
</div>
<div>
<Controller
{...control('plots')}
render={(params) => (
<CheckboxGroup
items={Object.values(FLORIS_PLOTS)}
getItemKey={(i) => i.name}
getItemLabel={(i) => i.label}
label="Plots"
{...params}
/>
)}
/>
</div>
</div>
<div className={styles.buttonBox}>
<Button type="submit" pending={pending}>
Submit
</Button>
<Button variant="secondary" onClick={handleResetButtonClick}>
Reset
</Button>
</div>
</form>
);
}

View File

@ -0,0 +1 @@
export * from './component';

View File

@ -0,0 +1,35 @@
.form {
display: grid;
padding: 20px;
border-radius: 15px;
background-color: var(--clr-layer-200);
box-shadow: 0px 1px 2px var(--clr-shadow-100);
gap: 20px;
& > * {
width: 100%;
}
}
.content {
display: grid;
gap: 30px;
grid-template-columns: 3fr 2fr;
}
.part {
display: grid;
gap: 10px;
}
.dateRangeBox {
display: grid;
gap: 10px;
grid-template-columns: 1fr 1fr;
}
.buttonBox {
display: flex;
justify-content: end;
gap: 10px;
}

View File

@ -0,0 +1,17 @@
import { WindmillData } from '@api/floris/types';
import { Park } from '@api/wind';
import { FlorisTableTurbine } from '../floris-table/types';
export type FlorisFormValues = {
dateFrom: string;
dateTo: string;
turbines: FlorisTableTurbine[];
plots: boolean[];
park: Park;
};
export type FlorisFormProps = {
onSuccess: (response: WindmillData, dateFrom: string) => void;
onFail: (message: string) => void;
} & React.ComponentProps<'form'>;

View File

@ -0,0 +1,24 @@
import { BASE_URL } from '@api/constants';
import { FLORIS_PLOTS } from '@api/floris';
import { Heading, Span } from '@components/ui';
import React from 'react';
import styles from './styles.module.scss';
import { FlorisPlotsProps } from './types';
export function FlorisPlots({ filenames }: FlorisPlotsProps) {
return (
<div className={styles.plots}>
<Heading tag="h3">Plots</Heading>
{Object?.keys(filenames).map((key) => {
const url = `${BASE_URL}/api/floris/download_image/${filenames[key]}`;
return (
<div className={styles.plot}>
<Span>{FLORIS_PLOTS[key]?.label ?? '???'}</Span>
<img src={url} className={styles.image} alt="Plot" />
</div>
);
})}
</div>
);
}

View File

@ -0,0 +1 @@
export * from './component';

View File

@ -0,0 +1,21 @@
.plots {
display: flex;
flex-direction: column;
padding: 20px;
border-radius: 15px;
background-color: var(--clr-layer-200);
box-shadow: 0px 1px 2px var(--clr-shadow-100);
gap: 20px;
}
.plot {
display: grid;
gap: 10px;
grid-template-columns: minmax(0, 1fr);
grid-template-rows: auto 1fr;
}
.image {
width: 100%;
border-radius: 10px;
}

View File

@ -0,0 +1,3 @@
export type FlorisPlotsProps = {
filenames: Record<string, string>;
};

View File

@ -0,0 +1,58 @@
import { IconButton, Span } from '@components/ui';
import DeleteIcon from '@public/images/svg/delete.svg';
import PlusIcon from '@public/images/svg/plus.svg';
import React, { useState } from 'react';
import { FlorisTableRow } from './components';
import styles from './styles.module.scss';
import { FlorisTableProps, FlorisTableTurbine } from './types';
export function FlorisTable({ value = [], onChange }: FlorisTableProps) {
const [selectedRows, setSelectedRows] = useState<Record<string, boolean>>({});
const handleDeleteButtonClick = () => {
onChange?.(value.filter((_, i) => !selectedRows[i]));
setSelectedRows({});
};
const handlePlusButtonClick = () => {
onChange?.([...value, { x: '', y: '', angle: '' }]);
};
const handleRowChange = (index: number, turbine: FlorisTableTurbine) => {
onChange?.(value.with(index, turbine));
};
const handleRowSelect = (index: number) => {
const checked = !selectedRows[index];
setSelectedRows({ ...selectedRows, [index]: checked });
};
return (
<div className={styles.table}>
<header className={styles.header}>
<Span className={styles.span} />
<Span className={styles.span}>x</Span>
<Span className={styles.span}>y</Span>
<Span className={styles.span}>angle</Span>
</header>
{value.map((v, i) => (
<FlorisTableRow
key={i}
value={v}
onChange={(turbine) => handleRowChange(i, turbine)}
onSelect={() => handleRowSelect(i)}
selected={selectedRows[i] ?? false}
/>
))}
<footer className={styles.footer}>
<IconButton onClick={handleDeleteButtonClick}>
<DeleteIcon />
</IconButton>
<IconButton onClick={handlePlusButtonClick}>
<PlusIcon />
</IconButton>
</footer>
</div>
);
}

View File

@ -0,0 +1,42 @@
import { Checkbox, NumberInput } from '@components/ui';
import React from 'react';
import { FlorisTableTurbine } from '../../types';
import styles from './styles.module.scss';
import { FlorisTableRowProps } from './types';
export function FlorisTableRow({
value,
onChange,
onSelect,
selected,
}: FlorisTableRowProps) {
const handleChange = (number: string, key: keyof FlorisTableTurbine) => {
onChange({ ...value, [key]: number });
};
return (
<div className={styles.row}>
<Checkbox
label={{ className: styles.checkboxLabel }}
onChange={onSelect}
checked={selected}
/>
<NumberInput
value={value.x}
onChange={(number) => handleChange(number, 'x')}
wrapper={{ className: styles.textInput }}
/>
<NumberInput
value={value.y}
onChange={(number) => handleChange(number, 'y')}
wrapper={{ className: styles.textInput }}
/>
<NumberInput
value={value.angle}
onChange={(number) => handleChange(number, 'angle')}
wrapper={{ className: styles.textInput }}
/>
</div>
);
}

View File

@ -0,0 +1 @@
export * from './component';

View File

@ -0,0 +1,16 @@
.row {
display: grid;
grid-template-columns: auto 1fr 1fr 1fr;
}
.checkboxLabel {
width: 46px;
justify-content: center;
border: 1px solid var(--clr-border-200);
}
.textInput {
border-radius: 0;
background-color: var(--clr-layer-200);
box-shadow: none;
}

View File

@ -0,0 +1,8 @@
import { FlorisTableTurbine } from '../../types';
export type FlorisTableRowProps = {
value: FlorisTableTurbine;
onChange: (value: FlorisTableTurbine) => void;
onSelect: () => void;
selected: boolean;
};

View File

@ -0,0 +1 @@
export * from './floris-table-row';

View File

@ -0,0 +1,32 @@
.table {
border-radius: 10px;
background-color: var(--clr-layer-200);
box-shadow: 0px 2px 2px var(--clr-shadow-100);
}
.header {
display: grid;
grid-template-columns: 46px 1fr 1fr 1fr;
}
.span {
padding: 13px;
border: 1px solid var(--clr-border-200);
background-color: var(--clr-layer-300);
text-align: center;
&:first-of-type {
border-top-left-radius: 10px;
}
&:last-of-type {
border-top-right-radius: 10px;
}
}
.footer {
padding: 5px;
border: 1px solid var(--clr-border-200);
border-radius: 0 0 10px 10px;
background-color: var(--clr-layer-300);
}

View File

@ -0,0 +1,10 @@
export type FlorisTableTurbine = {
x: string;
y: string;
angle: string;
};
export type FlorisTableProps = {
value?: FlorisTableTurbine[];
onChange?: (value: FlorisTableTurbine[]) => void;
};

View File

@ -1,5 +1,8 @@
export { FlorisForm } from './floris-form';
export { FlorisPlots } from './floris-plots';
export { Header } from './header';
export { ParkTurbineTable } from './park-turbine-table';
export { ParkTurbines } from './park-turbines';
export { PowerSection } from './power-section';
export { Sidebar } from './sidebar';
export { ThemeSelect } from './theme-select';

View File

@ -3,4 +3,5 @@ import { ROUTES } from '@utils/route';
export const NAVIGATION_LINKS = [
{ path: ROUTES.turbineTypes.path, title: ROUTES.turbineTypes.title },
{ path: ROUTES.parks.path, title: ROUTES.parks.title },
{ path: ROUTES.floris.path, title: ROUTES.floris.title },
];

View File

@ -0,0 +1,46 @@
import { Heading, Span } from '@components/ui';
import clsx from 'clsx';
import React from 'react';
import styles from './style.module.scss';
import { PowerSectionProps } from './types';
export function PowerSection({ power, dateFrom }: PowerSectionProps) {
const gridTemplateColumns = `repeat(${power[0].length + 1}, 1fr)`;
const date = new Date(dateFrom);
return (
<section className={styles.section}>
<Heading tag="h3">Power, watt per hour</Heading>
<div>
<div className={styles.row} style={{ gridTemplateColumns }}>
<Span className={clsx(styles.cell, styles.mainCell)}></Span>
{power[0].map((_, i) => (
<Span className={clsx(styles.cell, styles.mainCell)} key={i}>
{i + 1}
</Span>
))}
</div>
{power.map((row, r) => {
const day = String(date.getDate()).padStart(2, '0');
const month = String(date.getMonth() + 1).padStart(2, '0');
const year = date.getFullYear();
const dateStr = `${day}.${month}.${year}`;
date.setDate(date.getDate() + 1);
return (
<div className={styles.row} style={{ gridTemplateColumns }} key={r}>
<Span className={clsx(styles.cell, styles.mainCell)}>
{dateStr}
</Span>
{row.map((value, c) => (
<Span className={styles.cell} color="t300" key={c}>
{value}
</Span>
))}
</div>
);
})}
</div>
</section>
);
}

View File

@ -0,0 +1 @@
export * from './component';

View File

@ -0,0 +1,43 @@
.section {
display: grid;
padding: 20px;
border-radius: 15px;
background-color: var(--clr-layer-200);
box-shadow: 0px 1px 2px var(--clr-shadow-100);
gap: 20px;
}
.row {
display: grid;
&:first-child {
.cell {
&:first-of-type {
border-top-left-radius: 10px;
}
&:last-of-type {
border-top-right-radius: 10px;
}
}
}
&:last-child {
.cell {
&:first-of-type {
border-bottom-left-radius: 10px;
}
&:last-of-type {
border-bottom-right-radius: 10px;
}
}
}
}
.cell {
padding: 10px;
border: 1px solid var(--clr-border-200);
}
.mainCell {
background-color: var(--clr-layer-300);
}

View File

@ -0,0 +1,4 @@
export type PowerSectionProps = {
power: number[][];
dateFrom: string;
};

View File

@ -7,6 +7,7 @@ export const ROUTES: Record<AppRouteName, AppRoute> = {
turbineType: { path: '/turbine-types/:id', title: 'Turbine Type' },
parks: { path: '/parks', title: 'Parks' },
park: { path: '/parks/:id', title: 'Park' },
floris: { path: '/floris', title: 'Floris' },
};
export const routeArray = Object.values(ROUTES);

View File

@ -1,4 +1,9 @@
export type AppRouteName = 'turbineTypes' | 'turbineType' | 'parks' | 'park';
export type AppRouteName =
| 'turbineTypes'
| 'turbineType'
| 'parks'
| 'park'
| 'floris';
export type AppRoute = {
path: string;

View File

@ -26,8 +26,8 @@ class OpenMeteoClient:
return responses
def process_response(self, response):
# Process hourly data
daily = response.Daily()
daily_wind_speed_10m = daily.Variables(0).ValuesAsNumpy()
daily_wind_direction_10m = daily.Variables(1).ValuesAsNumpy()
@ -36,5 +36,4 @@ class OpenMeteoClient:
def get_weather_info(self, start_date, end_date, latitude=54.35119762746125, longitude=48.389356992149345):
responses = self.fetch_weather_data(latitude, longitude, start_date, end_date)
response = responses[0]
self.process_response(response)
return self.process_response(response)

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 125 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 141 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

Some files were not shown because too many files have changed in this diff Show More