Skip to content

Commit fff5192

Browse files
Merge pull request #29 from mapswipe/feature/tutorial-image-block
Add support for image block in tutorial information page
2 parents 07025a6 + c64106d commit fff5192

File tree

20 files changed

+709
-99
lines changed

20 files changed

+709
-99
lines changed

app/Base/styles.module.css

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -62,8 +62,8 @@ p {
6262

6363
--spacing-2xs: 0.125rem;
6464
--spacing-xs: 0.25rem;
65-
--spacing-sm: 0.375rem;
66-
--spacing-md: 0.625rem;
65+
--spacing-sm: 0.5rem;
66+
--spacing-md: 0.75rem;
6767
--spacing-lg: 1rem;
6868
--spacing-xl: 1.625rem;
6969
--spacing-2xl: 2.625rem;
@@ -133,11 +133,11 @@ p {
133133
--width-scrollbar: 0.75rem;
134134
--width-page-content-max: 80rem;
135135

136-
--width-mobile-preview: 22rem;
137-
--height-mobile-preview: 40rem;
138136

139-
--size-tile-preview: 10rem;
137+
--size-tile-preview: 160px;
140138
--size-compare-preview: 14rem;
139+
--width-mobile-preview: calc(var(--spacing-md) * 2 + var(--size-tile-preview) * 2);
140+
--height-mobile-preview: 40rem;
141141

142142
--height-mobile-preview-builarea-content: 30rem;
143143
--height-mobile-preview-footprint-content: 22rem;

app/components/MobilePreview/styles.module.css

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,13 @@
3131
display: flex;
3232
position: relative;
3333
flex-direction: column;
34-
padding: var(--spacing-md);
34+
margin: var(--spacing-md);
3535
overflow: auto;
3636
isolation: isolate;
3737

3838
.popup {
3939
position: absolute;
40-
top: var(--spacing-md);
40+
top: var(--spacing-sm);
4141
left: 0;
4242
z-index: 1;
4343
border-radius: var(--radius-card-border);

app/components/domain/BaseMap/index.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,14 @@ const defaultMapOptions: Omit<maplibregl.MapOptions, 'container' | 'style' | 'ch
2626
interface Props {
2727
baseTileServer: PartialRasterTileServerInputFields | undefined;
2828
children?: React.ReactNode;
29+
tileSize?: number;
2930
}
3031

3132
function BaseMap(props: Props) {
3233
const {
3334
baseTileServer,
3435
children,
36+
tileSize = 256,
3537
} = props;
3638

3739
const { raster: rasterTileServers } = useContext(TileServerContext);
@@ -85,7 +87,7 @@ function BaseMap(props: Props) {
8587
'base-tile-source': {
8688
type: 'raster',
8789
tiles: [url],
88-
tileSize: 256,
90+
tileSize,
8991
attribution: credits ?? '',
9092
},
9193
},
@@ -95,7 +97,7 @@ function BaseMap(props: Props) {
9597
source: 'base-tile-source',
9698
}],
9799
};
98-
}, [url, credits]);
100+
}, [url, tileSize, credits]);
99101

100102
if (isNotDefined(mapStyle)) {
101103
return null;

app/components/domain/GeoJsonMapSource/index.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,9 @@ const geoJsonSourceOptions: Omit<maplibregl.GeoJSONSourceSpecification, 'data'>
2121
const geoJsonLayerOptions: ComponentProps<typeof MapLayer>['layerOptions'] = {
2222
type: 'line',
2323
paint: {
24-
'line-color': '#ffff00',
24+
'line-color': '#ffffff',
2525
'line-width': 2,
26-
'line-dasharray': [2, 1],
26+
// 'line-dasharray': [2, 1],
2727
},
2828
};
2929

app/components/domain/GeoJsonPreview/index.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ interface Props {
3838
| undefined;
3939
geoJsonLayerOptions?: ComponentProps<typeof MapLayer>['layerOptions'];
4040
padding?: number;
41+
tileSize?: number;
4142
}
4243

4344
function GeoJsonPreview(props: Props) {
@@ -47,13 +48,15 @@ function GeoJsonPreview(props: Props) {
4748
geoJson,
4849
geoJsonLayerOptions = defaultGeoJsonLayerOptions,
4950
padding = DEFAULT_MAP_PADDING,
51+
tileSize,
5052
} = props;
5153

5254
const bounds = isDefined(geoJson) ? getBbox(geoJson) : undefined;
5355

5456
return (
5557
<BaseMap
5658
baseTileServer={baseTileServer}
59+
tileSize={tileSize}
5760
>
5861
{isDefined(geoJson) && (
5962
<MapSource
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
import {
2+
useCallback,
3+
useId,
4+
} from 'react';
5+
import {
6+
isDefined,
7+
isNotDefined,
8+
} from '@togglecorp/fujs';
9+
import { ulid } from 'ulid';
10+
import { gql } from 'urql';
11+
12+
import TutorialAssetPreview from '#components/domain/TutorialAssetPreview';
13+
import FileInput from '#components/FileInput';
14+
import InputContainerLayout, { Props as InputContainerLayoutProps } from '#components/InputContainerLayout';
15+
import {
16+
AssetMimetypeEnum,
17+
useCreateTutorialAssetMutation,
18+
} from '#generated/types/graphql';
19+
import { OPERATION_INFO_FRAGMENT } from '#utils/query';
20+
21+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
22+
const CREATE_tutorial_ASSET_MUTATION = gql`
23+
${OPERATION_INFO_FRAGMENT}
24+
mutation CreateTutorialAsset($data: TutorialAssetCreateInput!) {
25+
createTutorialAsset(data: $data) {
26+
... on TutorialAssetTypeMutationResponseType {
27+
__typename
28+
errors
29+
ok
30+
result {
31+
id
32+
}
33+
}
34+
... on OperationInfo {
35+
...OperationInfoFields
36+
}
37+
}
38+
}
39+
`;
40+
41+
interface Props<NAME> extends Omit<InputContainerLayoutProps, 'children' | 'inputId'> {
42+
name: NAME,
43+
tutorialId: string;
44+
value: string | undefined | null;
45+
onChange: (newValue: string | undefined, name: NAME) => void;
46+
selectFileButtonLabel?: React.ReactNode;
47+
className?: string;
48+
disabled?: boolean;
49+
inputType?: 'geojson' | 'image';
50+
withoutPreview?: boolean;
51+
}
52+
53+
function TutorialAssetInput<const NAME>(props: Props<NAME>) {
54+
const {
55+
className,
56+
name,
57+
tutorialId,
58+
value,
59+
onChange,
60+
disabled,
61+
inputType = 'geojson',
62+
selectFileButtonLabel = inputType === 'image'
63+
? 'Select an image'
64+
: 'Select geojson',
65+
withoutPreview,
66+
...inputLayoutContainerProps
67+
} = props;
68+
69+
const inputId = useId();
70+
const [
71+
{ fetching: createTutorialAssetPending },
72+
createTutorialAsset,
73+
] = useCreateTutorialAssetMutation();
74+
75+
const handleFileInputChange = useCallback(async (file: File | undefined) => {
76+
if (file) {
77+
const { type } = file;
78+
const mimetypeEnumMap: Record<string, AssetMimetypeEnum> = {
79+
'image/jpeg': AssetMimetypeEnum.ImageJpeg,
80+
'image/png': AssetMimetypeEnum.ImagePng,
81+
'image/gif': AssetMimetypeEnum.ImageGif,
82+
'application/geo+json': AssetMimetypeEnum.Geojson,
83+
};
84+
85+
const selectedEnum = mimetypeEnumMap[type];
86+
87+
if (isNotDefined(selectedEnum)) {
88+
// eslint-disable-next-line no-console
89+
console.error('Invalid file selected!');
90+
return;
91+
}
92+
93+
const result = await createTutorialAsset({
94+
data: {
95+
clientId: ulid(),
96+
file,
97+
mimetype: selectedEnum,
98+
tutorial: tutorialId,
99+
},
100+
});
101+
102+
if (
103+
// eslint-disable-next-line no-underscore-dangle
104+
result.data?.createTutorialAsset.__typename === 'TutorialAssetTypeMutationResponseType'
105+
&& result.data.createTutorialAsset.ok
106+
&& result.data.createTutorialAsset.result
107+
) {
108+
onChange(result.data.createTutorialAsset.result.id, name);
109+
} else {
110+
// eslint-disable-next-line no-underscore-dangle
111+
const errorMessage = result.data
112+
?.createTutorialAsset?.__typename === 'OperationInfo'
113+
? result.data.createTutorialAsset.messages
114+
: result.data?.createTutorialAsset?.errors?.[0]?.message
115+
|| 'Failed to upload file. Please try again.';
116+
// eslint-disable-next-line no-console
117+
console.error(errorMessage);
118+
}
119+
}
120+
}, [createTutorialAsset, tutorialId, onChange, name]);
121+
122+
return (
123+
<InputContainerLayout
124+
inputId={inputId}
125+
className={className}
126+
// eslint-disable-next-line react/jsx-props-no-spreading
127+
{...inputLayoutContainerProps}
128+
>
129+
<FileInput
130+
name={undefined}
131+
inputId={inputId}
132+
type="file"
133+
value={undefined}
134+
onChange={handleFileInputChange}
135+
accept={inputType === 'geojson' ? '.geojson' : 'image/png, image/gif, image/jpeg'}
136+
disabled={disabled || createTutorialAssetPending}
137+
selectButtonLabel={selectFileButtonLabel}
138+
status={isDefined(value) ? '1 file selected' : 'No file selected'}
139+
>
140+
{!withoutPreview && isDefined(value) && (
141+
<TutorialAssetPreview
142+
assetId={value}
143+
/>
144+
)}
145+
</FileInput>
146+
</InputContainerLayout>
147+
);
148+
}
149+
150+
export default TutorialAssetInput;

0 commit comments

Comments
 (0)