Skip to content

Commit

Permalink
ADD: zoom option for multiple icon sizes
Browse files Browse the repository at this point in the history
  • Loading branch information
fileformat committed Aug 2, 2024
1 parent a8b3faa commit a6cfc48
Show file tree
Hide file tree
Showing 5 changed files with 153 additions and 38 deletions.
59 changes: 29 additions & 30 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,40 +1,39 @@
# Welcome to Remix!
# SVG View [<img alt="Logo for SVG View" src="https://view.svg.zone/favicon.svg" height="96" align="right"/>](https://view.svg.zone/view.html?url=https%3A%2F%2Fview.svg.zone%2Fimages%2Fbanner.svg&zoom=max)

- 📖 [Remix docs](https://remix.run/docs)
[![deploy](https://github.com/VectorLogoZone/svgview/actions/workflows/gcr-deploy.yaml/badge.svg)](https://github.com/VectorLogoZone/svgview/actions/workflows/gcr-deploy.yaml)

## Development
[demo](https://view.svg.zone/view.html?url=https%3A%2F%2Fgithub.com%2FVectorLogoZone%2Fsvgview%2Factions%2Fworkflows%2Fgcr-deploy.yaml%2Fbadge.svg&zoom=max&backUrl=https%3A%2F%2Fgithub.com%2FVectorLogoZone%2Fsvgview&backText=Return+to+Github)

Run the dev server:
## Integration

```shellscript
npm run dev
```
You can use this as a viewer from any website by passing the correct query string values. Be sure the [encode](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent) them!

## Deployment
* `backText` - tooltip text for the Exit button
* `backUrl` - destination for the Exit button
* `border` - border: one of "none", "dash", "thin" or "thick"
* `bg` - background: a hex color (like "#abc123", but note that the "#" has to be escaped to "%23") or one of the [standard images](public/images/backgrounds/) (without the ".png" extension)
* `url` - URL of the SVG file
* `zoom` - zoom level: a number or "max"

First, build your app for production:
## License

```sh
npm run build
```
[MIT](LICENSE.txt)

Then run the app in production mode:
## Credits

```sh
npm start
```
[![Chakra UI](https://www.vectorlogo.zone/logos/chakra-ui/chakra-ui-ar21.svg)](https://v2.chakra-ui.com/ "UI Framework")
[![Google CloudRun](https://www.vectorlogo.zone/logos/google_cloud_run/google_cloud_run-ar21.svg)](https://cloud.google.com/run/ "Hosting")
[![Git](https://www.vectorlogo.zone/logos/git-scm/git-scm-ar21.svg)](https://git-scm.com/ "Version control")
[![Github](https://www.vectorlogo.zone/logos/github/github-ar21.svg)](https://github.com/ "Code hosting")
[![golang](https://www.vectorlogo.zone/logos/golang/golang-ar21.svg)](https://golang.org/ "Programming language")
[![Google Noto Emoji](https://www.vectorlogo.zone/logos/google/google-ar21.svg)](https://github.com/googlefonts/noto-emoji/blob/master/svg/emoji_u1f441.svg "Logo/Favicon")
[![NodePing](https://www.vectorlogo.zone/logos/nodeping/nodeping-ar21.svg)](https://nodeping.com?rid=201109281250J5K3P "Uptime monitoring")
[![npm](https://www.vectorlogo.zone/logos/npmjs/npmjs-ar21.svg)](https://www.npmjs.com/ "JS Package Management")
[![Phosphor Icons](https://www.vectorlogo.zone/logos/phosphoricons/phosphoricons-ar21.svg)](https://phosphoricons.com/ "Toolbar icons")
[![react.js](https://www.vectorlogo.zone/logos/reactjs/reactjs-ar21.svg)](https://reactjs.org/ "UI Framework")
[![Remix](https://www.vectorlogo.zone/logos/remix/remix-ar21.svg)](https://remix.run/ "React Framework")
[![TopTal](https://www.vectorlogo.zone/logos/toptal/toptal-ar21.svg)](https://www.toptal.com/designers/subtlepatterns/ "Background pattern")
[![TypeScript](https://www.vectorlogo.zone/logos/typescriptlang/typescriptlang-ar21.svg)](https://www.typescriptlang.org/ "Programming Language")
[![VectorLogoZone](https://www.vectorlogo.zone/logos/vectorlogozone/vectorlogozone-ar21.svg)](https://www.vectorlogo.zone/ "Logos")
[![Vite](https://www.vectorlogo.zone/logos/vitejsdev/vitejsdev-ar21.svg)](https://vitejs.dev/ "Bundler")

Now you'll need to pick a host to deploy it to.

### DIY

If you're familiar with deploying Node applications, the built-in Remix app server is production-ready.

Make sure to deploy the output of `npm run build`

- `build/server`
- `build/client`

## Styling

This template comes with [Tailwind CSS](https://tailwindcss.com/) already configured for a simple default starting experience. You can use whatever css framework you prefer. See the [Vite docs on css](https://vitejs.dev/guide/features.html#css) for more information.
48 changes: 48 additions & 0 deletions TODO.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# To Do

- [ ] show `error` search param
- [ ] keyboard support
- [ ] multi-image icon view

## General

- [ ] clean mobile layout for non-view pages
- [ ] 404 handler
- [ ] /info.html - info about the current SVG
- url
- dimensions
- colors
- contains [ text | transforms | rasters | foreignObjects | namespaces | ... ]
- size (KB)
- metadata
- [ ] privacy policy, terms of service, etc
- [ ] convert to PNG
- [ ] convert to PDF
- [ ] support for multiple images with next/prev buttons, thumbnail view (see [qimgv](https://github.com/easymodo/qimgv))
- [ ] upload page and post endpoint (loads SVG into temporary server url then redirects)
- [ ] shell script for post endpoint

## Preview
- [ ] `icon` zoom mode (PiChartBarFill) that shows multiple fixes sizes (16, 32...)
- [ ] `%` zoom mode (no toolbar button: only for incoming links)
- [ ] support a second exit parameter (`src`?) that goes to the hosting webpage (PiCircuitryBold, PiCubeFocus, PiLinkBold, PiOrangeBold, PiSignPostBold)
- [ ] `info` parameter (and toolbar button) with markdown (instead of `src` param?)
- [ ] rotate
- [ ] flip
- [ ] background color picker
- [ ] image info (PiInfoBold, PiRuler)
- [ ] edit (but which editor? configurable?) (PiPencil)
- [ ] share (PiShareNetworkFill)
- [ ] download (PiDownloadBold, PiBoxArrowDownBold)
- [ ] source (PiCodeBold or PiFileCode)
- [ ] double-click: zoom in, shift: zoom out
- [ ] drag rectangle: zoom
- [ ] keyboard control (see [qimgv](https://github.com/easymodo/qimgv))
- [ ] mouse control
- [ ] mobile pinch to zoom in/out [library](https://www.npmjs.com/package/react-map-interaction)
- [ ] copy to clipboard
- [ ] PiSignOutBold for exit icon?
- [ ] `diff` mode: select a second image and use the WebAwesome [ImageComparer](https://backers.webawesome.com/docs/components/image-comparer) widget


[Phosphor icons](https://react-icons.github.io/react-icons/icons/pi/)
55 changes: 55 additions & 0 deletions app/routes/view[.]html/IconList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { VStack, Stack, Text, useBreakpointValue, StackDirection } from "@chakra-ui/react";


const sizes = [ 16, 24, 32, 48, 64, 96, 128 ]

interface ICardProps {
imageCss: Record<string, string>;
size: number;
url: string;
}

function IconCard({ imageCss, size, url }: ICardProps) {
const alignSelf = useBreakpointValue({
base: 'center',
lg: 'end',
}, {
fallback: 'lg',
}) || 'start';

return (
<VStack alignSelf={alignSelf}>
<img alt={`${size} for ${url}`} src={url} style={{...imageCss, "width": `${size}pt`, "height": `${size}pt`}}/>
<Text>{`${size}`}</Text>
</VStack>
)
}

interface IProps {
display: string,
imageCss: Record<string, string>;
url: string;
}


function IconList({
display,
imageCss,
url,
}: IProps) {

const direction:StackDirection = useBreakpointValue({
base: 'column',
lg: 'row',
}, {
fallback: 'lg',
}) || 'row';

return(
<Stack direction={direction} justifyItems="center" spacing={10} style={{"display": display}}>
{sizes.map((size) => ( <IconCard imageCss={imageCss} key={size} size={size} url={url} /> ))}
</Stack>
);
}

export { IconList };
12 changes: 11 additions & 1 deletion app/routes/view[.]html/ZoomButtons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
PiMagnifyingGlassMinusBold,
PiMagnifyingGlassPlusBold,
PiArrowsOutCardinalBold,
PiChartBarFill,
} from "react-icons/pi";
import { useSearchParams } from "@remix-run/react";

Expand Down Expand Up @@ -41,7 +42,7 @@ export const ZoomButtons = ({ currentZoom, size, boxSize }: IProps) => {
boxSize={boxSize}
param="zoom"
value="1"
isActive={currentZoom === 1}
isActive={(searchParams.get("zoom") || "1") === "1"}
icon={PiArrowsCounterClockwiseBold}
/>
<ToolbarButton
Expand All @@ -53,6 +54,15 @@ export const ZoomButtons = ({ currentZoom, size, boxSize }: IProps) => {
icon={PiMagnifyingGlassPlusBold}
isActive={false}
/>
<ToolbarButton
ariaLabel="Icons"
boxSize={boxSize}
param="zoom"
value="icons"
size={size}
icon={PiChartBarFill}
isActive={searchParams.get("zoom") === "icons"}
/>
<ToolbarButton
ariaLabel="Max zoom"
boxSize={boxSize}
Expand Down
17 changes: 10 additions & 7 deletions app/routes/view[.]html/route.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,11 @@ import { safeParseFloat } from "~/utils/safeParseFloat";
import { DesktopToolbar } from "./DesktopToolbar";
import { MobileToolbar } from "./MobileToolbar";
import { calcMaxZoom } from "./calcMaxZoom";
import { IconList } from "./IconList";

export default function ViewPage() {
const [ searchParams ] = useSearchParams();
const url = searchParams.get("url") || undefined;
const url = searchParams.get("url") || "";

const [naturalWidth, setNaturalWidth] = React.useState(1);
const [naturalHeight, setNaturalHeight] = React.useState(1);
Expand All @@ -31,8 +32,6 @@ export default function ViewPage() {
const imageRef = useRef<HTMLImageElement>(null);
const containerRef = useRef<HTMLDivElement>(null);

//const imageSize = useSize(imageRef);
//const containerSize = useSize(containerRef);
const imageCss: Record<string, string> = {};
const noscriptImageCss: Record<string, string> = {};
let noscriptHeight:string|undefined;
Expand All @@ -43,6 +42,8 @@ export default function ViewPage() {
currentZoom = calcMaxZoom(naturalWidth, naturalHeight, containerRef);
noscriptImageCss["objectFit"] = "cover";
noscriptHeight = "99%";
} else if (urlZoom === "icons") {
// do nothing
} else {
noscriptImageCss["transform"] = `scale(${currentZoom})`;
}
Expand Down Expand Up @@ -97,7 +98,7 @@ export default function ViewPage() {
setLoading(false);
setNaturalWidth(imageRef.current?.naturalWidth || 1);
setNaturalHeight(imageRef.current?.naturalHeight || 1);
setImageDisplay( 'block' );
setImageDisplay( 'flex' );
}, [ imageRef ]);

const onImageError = useCallback(() => {
Expand Down Expand Up @@ -139,7 +140,7 @@ export default function ViewPage() {
alignItems="center"
justifyContent="center"
style={{
overflow: "hidden",
overflow: ( urlZoom == "icons" ? "scroll" : "hidden" ),
...background,
}}
>
Expand All @@ -159,7 +160,7 @@ export default function ViewPage() {
{loading ? (
<Spinner className="scriptonly" size="xl" />
) : (
<img
(urlZoom === "icons" ? <IconList display={imageDisplay} imageCss={imageCss} url={url} /> : <img
alt={url}
src={url}
style={{
Expand All @@ -169,7 +170,7 @@ export default function ViewPage() {
...imageCss,
}}
title={url}
/>
/>)
)}
<img
alt={`${url} (preload/debug)`}
Expand All @@ -184,11 +185,13 @@ export default function ViewPage() {
right: 0,
}}
/><noscript style={{ "height": noscriptHeight, "display": "flex"}}>
{(urlZoom === "icons" ? <IconList display="flex" imageCss={noscriptImageCss} url={url} /> :
<img
alt={url}
src={url}
style={{...noscriptImageCss}}
/>
)}
</noscript>
{isDebug && (
<Text position={"absolute"} top={"14pt"} left={2}>
Expand Down

0 comments on commit a6cfc48

Please sign in to comment.