Skip to content

Commit

Permalink
Merge pull request #31 from amplience/dev
Browse files Browse the repository at this point in the history
  • Loading branch information
rezakalfane authored Sep 25, 2023
2 parents cb82a8c + a3adcf9 commit a9a1db4
Show file tree
Hide file tree
Showing 43 changed files with 1,284 additions and 156 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ In your Vercel project browse to Settings --> Environment Variables and edit the


## Additional Topics
- [Features Highlights](docs/FeatureHiLites.md)
- [Features Highlights](docs/FeatureHighlights.md)
- [High-Level Architecture](docs/ArchDiagram.md)
- [Available Components](docs/Components.md)
- [Exploring features](docs/DeepDive.md)
Expand Down
24 changes: 21 additions & 3 deletions components/admin/AdminPanel/AdminPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,28 @@ import VisibilityIcon from '@mui/icons-material/Visibility';
import Accordion from '@mui/material/Accordion';
import AccordionSummary from '@mui/material/AccordionSummary';
import AccordionDetails from '@mui/material/AccordionDetails';
import ElectricBoltIcon from '@mui/icons-material/ElectricBolt';
import { withStyles, WithStyles } from '@mui/styles'

import WithAdminTheme from '@components/admin/AdminTheme';
import ComponentsPanel from './panels/ComponentsPanel';
import ContentPreviewPanel from './panels/ContentPreviewPanel';
import { getHubName } from '@lib/config/locator/config-locator';
import { useECommerce } from '@components/core/Masthead/ECommerceContext';
import AcceleratedMediaPanel from './panels/AcceleratedMediaPanel';

const styles = (theme: Theme) => ({
root: {
},
logo: {
display: 'flex',
padding: '10px 10px 4px 10px',
justifyContent: 'left'
justifyContent: 'center'
},
environment: {
display: 'flex',
justifyContent: 'left',
padding: '8px'
},
icon: {
marginRight: '0.4rem',
Expand Down Expand Up @@ -50,11 +57,12 @@ const AdminPanel: React.FunctionComponent<Props> = (props) => {
<Image src="/images/amplience.png" width={247} height={100} alt='amplience' />
</div>
<Divider />
<div className={classes.logo}>
<div className={classes.environment}>
<div>
<span>hub</span> <span><b>{hubname}</b></span>
</div>
<div style={{ marginLeft: '40px' }}>
<div style={{flexGrow: 1}} />
<div style={{justifyContent: 'right'}}>
<span>vendor</span> <span><b>{vendor}</b></span>
</div>
</div>
Expand All @@ -78,6 +86,16 @@ const AdminPanel: React.FunctionComponent<Props> = (props) => {
<ComponentsPanel />
</AccordionDetails>
</Accordion>

<Accordion key={'Accelerated Media'}>
<AccordionSummary expandIcon={<ExpandMoreIcon />} aria-controls="panel1a-content">
<ElectricBoltIcon className={classes.icon} />
<Typography variant="button">{'Accelerated Media'}</Typography>
</AccordionSummary>
<AccordionDetails>
<AcceleratedMediaPanel />
</AccordionDetails>
</Accordion>
</div>
</WithAdminTheme>
);
Expand Down
126 changes: 126 additions & 0 deletions components/admin/AdminPanel/ImageStatistics.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
export interface ImageStatistics {
src: string;
name: string;
types: { [key: string]: string }
sizes: { [key: string]: number }
auto: string;
completed: number,
total: number
}

const formatTests = ['auto', 'jpeg', 'webp', 'avif']; // png deliberately excluded

export const formatColors: { [key: string]: string } = {
jpeg: '#FFA200',
webp: '#00B6FF',
avif: '#65CC02',
auto: '#8F9496',
png: '#E94420'
}

export const typeFromFormat: { [key: string]: string } = {
'image/webp': 'webp',
'image/jpeg': 'jpeg',
'image/avif': 'avif',
'image/png': 'png'
};


export function isValid(stat: ImageStatistics, key: string): boolean {
let type = stat.types[key];
let realKey = typeFromFormat[type] ?? key;

return key === 'auto' || key === realKey;
}

export function hasInvalid(stat: ImageStatistics): boolean {
for (const key of Object.keys(stat.sizes)) {
if (!isValid(stat, key)) {
return true;
}
}

return false;
}

function getAcceptHeader(): string {
// TODO: guess accept header based on browser version?
return 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8';
}

export async function DetermineImageSizes(onChange: (stats: ImageStatistics[]) => void) {
const images = Array.from(document.images);

const uniqueSrc = new Set<string>();
const result: ImageStatistics[] = [];

const promises: Promise<any>[] = [];

for (const image of images) {
const src = image.currentSrc;

if (uniqueSrc.has(src)) {
continue;
}

uniqueSrc.add(src);

try {
const url = new URL(src);

const isAmplienceRequest = url.pathname.startsWith('/i/') || url.pathname.startsWith('/s/');
const accountName = url.pathname.split('/')[2];

if (isAmplienceRequest) {
const imageResult: ImageStatistics = {
src,
name: url.pathname.split('/')[3],
types: {},
sizes: {},
completed: 0,
auto: 'none',
total: formatTests.length
}

result.push(imageResult);

onChange(result);

const formatPromises = formatTests.map(async format => {
url.searchParams.set('fmt', format);

const src = url.toString();

try {
const response = await fetch(src, { headers: { Accept: getAcceptHeader() }});

const headLength = response.headers.get("content-length");
const size = headLength ? Number(headLength) : (await response.arrayBuffer()).byteLength;

imageResult.sizes[format] = size;
imageResult.types[format] = response.headers.get("content-type") ?? '';
imageResult.completed++;

if (format === 'auto') {
imageResult.auto = typeFromFormat[imageResult.types[format]] ?? 'none'
}

onChange(result);
} catch (e) {
console.log(`Could not scan image ${image.currentSrc}`);
}
});

promises.push(...formatPromises);
}
} catch (e) {
console.log(`Not a valid URL ${image.currentSrc}`);
}
}

onChange(result);

await Promise.all(promises);

return result;
}
103 changes: 103 additions & 0 deletions components/admin/AdminPanel/ImageStatisticsBars.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import React, { FC } from 'react'
import { ImageStatistics, typeFromFormat, formatColors } from './ImageStatistics';
import { Theme, Tooltip } from '@mui/material';
import { WithStyles, withStyles } from '@mui/styles';

const styles = (theme: Theme) => ({
container: {
width: '100%',
display: 'flex',
flexDirection: 'column' as 'column'
},
barBase: {
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
color: '#444444',
height: '20px',
margin: '2px 0',
fontSize: '12px',
gap: '5px'
},
format: {
fontSize: '12px',
marginLeft: '4px',
whiteSpace: 'nowrap' as 'nowrap'
},
size: {
fontSize: '12px',
marginRight: '4px',
}
});

interface Props extends WithStyles<typeof styles> {
stat: ImageStatistics;
}

interface OrderedFormat {
key: string,
size: number,
auto: boolean,
realKey: string | null
}

function getRealType(stat: ImageStatistics, key: string): string | null {
let type = stat.types[key];

const realKey = typeFromFormat[type] ?? key;

return key === 'auto' || realKey == key ? null : realKey;
}

function getOrderedFormats(stat: ImageStatistics): OrderedFormat[] {
// Formats ordered by size.
const formatSizes = Object.keys(stat.sizes)
.sort()
.filter(key => key !== 'auto')
.map(key => ({
key,
size: stat.sizes[key],
same: [key],
auto: key === stat.auto,
realKey: getRealType(stat, key)
}));

formatSizes.sort((a, b) => a.size - b.size);

return formatSizes;
}

const ImageStatisticsBars: FC<Props> = ({stat, classes}) => {
const ordered = getOrderedFormats(stat);
const maxSize = ordered[ordered.length - 1].size;
const maxKey = ordered[ordered.length - 1].key;
// ordered.reverse();

return <div className={classes.container}>
{
ordered.map((elem, index) => {
const size = elem.size;
const name = elem.key;
const invalid = elem.realKey != null;
const titleName = invalid ? `"${name}" (got ${elem.realKey})` : name;
const title = `${titleName}: ${elem.size} bytes (${Math.round(1000 * elem.size / maxSize) / 10}% of ${maxKey})`;

return <Tooltip key={elem.key} title={title}>
<div className={classes.barBase} style={{
backgroundColor: formatColors[invalid ? 'auto' : elem.key],
width: `${(size / maxSize) * 100}%`,
outline: invalid ? '1px solid red' : ''
}}>
<span>
<span className={classes.format} style={{textDecoration: invalid ? 'line-through' : ''}}>{`${name}${elem.auto ? ' (auto)' : ''}`}</span>
{invalid ? <span className={classes.format}>{elem.realKey}</span> : null}
</span>
<span className={classes.size}>{elem.size}</span>
</div>
</Tooltip>
})
}
</div>
}

export default withStyles(styles)(ImageStatisticsBars);
Loading

10 comments on commit a9a1db4

@vercel
Copy link

@vercel vercel bot commented on a9a1db4 Sep 25, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@vercel
Copy link

@vercel vercel bot commented on a9a1db4 Sep 25, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@vercel
Copy link

@vercel vercel bot commented on a9a1db4 Sep 25, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@vercel
Copy link

@vercel vercel bot commented on a9a1db4 Sep 25, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

demo-ufatrial – ./

demo-ufatrial-amplience.vercel.app
demo-ufatrial.vercel.app
demo-ufatrial-git-main-amplience.vercel.app

@vercel
Copy link

@vercel vercel bot commented on a9a1db4 Sep 25, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@vercel
Copy link

@vercel vercel bot commented on a9a1db4 Sep 25, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@vercel
Copy link

@vercel vercel bot commented on a9a1db4 Sep 25, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@vercel
Copy link

@vercel vercel bot commented on a9a1db4 Sep 25, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@vercel
Copy link

@vercel vercel bot commented on a9a1db4 Sep 25, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@vercel
Copy link

@vercel vercel bot commented on a9a1db4 Sep 25, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.