This commit is contained in:
Chris Watson 2024-08-14 22:09:28 +00:00 committed by GitHub
commit 1f7f1cd697
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 6755 additions and 5100 deletions

34
components.d.ts vendored
View file

@ -13,6 +13,7 @@ declare module '@vue/runtime-core' {
About: typeof import('./src/pages/About.vue')['default']
App: typeof import('./src/App.vue')['default']
AsciiTextDrawer: typeof import('./src/tools/ascii-text-drawer/ascii-text-drawer.vue')['default']
AspectRatioCalculator: typeof import('./src/tools/aspect-ratio-calculator/aspect-ratio-calculator.vue')['default']
'Base.layout': typeof import('./src/layouts/base.layout.vue')['default']
Base64FileConverter: typeof import('./src/tools/base64-file-converter/base64-file-converter.vue')['default']
Base64StringConverter: typeof import('./src/tools/base64-string-converter/base64-string-converter.vue')['default']
@ -89,17 +90,28 @@ declare module '@vue/runtime-core' {
HttpStatusCodes: typeof import('./src/tools/http-status-codes/http-status-codes.vue')['default']
IbanValidatorAndParser: typeof import('./src/tools/iban-validator-and-parser/iban-validator-and-parser.vue')['default']
'IconMdi:brushVariant': typeof import('~icons/mdi/brush-variant')['default']
'IconMdi:contentCopy': typeof import('~icons/mdi/content-copy')['default']
'IconMdi:kettleSteamOutline': typeof import('~icons/mdi/kettle-steam-outline')['default']
IconMdiArrowDown: typeof import('~icons/mdi/arrow-down')['default']
IconMdiArrowRightBottom: typeof import('~icons/mdi/arrow-right-bottom')['default']
IconMdiCamera: typeof import('~icons/mdi/camera')['default']
IconMdiChevronDown: typeof import('~icons/mdi/chevron-down')['default']
IconMdiChevronRight: typeof import('~icons/mdi/chevron-right')['default']
IconMdiClose: typeof import('~icons/mdi/close')['default']
IconMdiContentCopy: typeof import('~icons/mdi/content-copy')['default']
IconMdiDeleteOutline: typeof import('~icons/mdi/delete-outline')['default']
IconMdiDownload: typeof import('~icons/mdi/download')['default']
IconMdiEye: typeof import('~icons/mdi/eye')['default']
IconMdiEyeOff: typeof import('~icons/mdi/eye-off')['default']
IconMdiHeart: typeof import('~icons/mdi/heart')['default']
IconMdiPause: typeof import('~icons/mdi/pause')['default']
IconMdiPlay: typeof import('~icons/mdi/play')['default']
IconMdiRecord: typeof import('~icons/mdi/record')['default']
IconMdiRefresh: typeof import('~icons/mdi/refresh')['default']
IconMdiSearch: typeof import('~icons/mdi/search')['default']
IconMdiTranslate: typeof import('~icons/mdi/translate')['default']
IconMdiTriangleDown: typeof import('~icons/mdi/triangle-down')['default']
IconMdiVideo: typeof import('~icons/mdi/video')['default']
InputCopyable: typeof import('./src/components/InputCopyable.vue')['default']
IntegerBaseConverter: typeof import('./src/tools/integer-base-converter/integer-base-converter.vue')['default']
Ipv4AddressConverter: typeof import('./src/tools/ipv4-address-converter/ipv4-address-converter.vue')['default']
@ -127,18 +139,40 @@ declare module '@vue/runtime-core' {
MenuLayout: typeof import('./src/components/MenuLayout.vue')['default']
MetaTagGenerator: typeof import('./src/tools/meta-tag-generator/meta-tag-generator.vue')['default']
MimeTypes: typeof import('./src/tools/mime-types/mime-types.vue')['default']
NAlert: typeof import('naive-ui')['NAlert']
NavbarButtons: typeof import('./src/components/NavbarButtons.vue')['default']
NCheckbox: typeof import('naive-ui')['NCheckbox']
NCode: typeof import('naive-ui')['NCode']
NCollapseTransition: typeof import('naive-ui')['NCollapseTransition']
NColorPicker: typeof import('naive-ui')['NColorPicker']
NConfigProvider: typeof import('naive-ui')['NConfigProvider']
NDatePicker: typeof import('naive-ui')['NDatePicker']
NDivider: typeof import('naive-ui')['NDivider']
NDynamicInput: typeof import('naive-ui')['NDynamicInput']
NEllipsis: typeof import('naive-ui')['NEllipsis']
NForm: typeof import('naive-ui')['NForm']
NFormItem: typeof import('naive-ui')['NFormItem']
NGi: typeof import('naive-ui')['NGi']
NGrid: typeof import('naive-ui')['NGrid']
NH1: typeof import('naive-ui')['NH1']
NH2: typeof import('naive-ui')['NH2']
NH3: typeof import('naive-ui')['NH3']
NIcon: typeof import('naive-ui')['NIcon']
NImage: typeof import('naive-ui')['NImage']
NInputGroup: typeof import('naive-ui')['NInputGroup']
NInputGroupLabel: typeof import('naive-ui')['NInputGroupLabel']
NInputNumber: typeof import('naive-ui')['NInputNumber']
NLayout: typeof import('naive-ui')['NLayout']
NLayoutSider: typeof import('naive-ui')['NLayoutSider']
NMenu: typeof import('naive-ui')['NMenu']
NProgress: typeof import('naive-ui')['NProgress']
NScrollbar: typeof import('naive-ui')['NScrollbar']
NSlider: typeof import('naive-ui')['NSlider']
NSpin: typeof import('naive-ui')['NSpin']
NStatistic: typeof import('naive-ui')['NStatistic']
NSwitch: typeof import('naive-ui')['NSwitch']
NTable: typeof import('naive-ui')['NTable']
NTag: typeof import('naive-ui')['NTag']
NumeronymGenerator: typeof import('./src/tools/numeronym-generator/numeronym-generator.vue')['default']
OtpCodeGeneratorAndValidator: typeof import('./src/tools/otp-code-generator-and-validator/otp-code-generator-and-validator.vue')['default']
PasswordStrengthAnalyser: typeof import('./src/tools/password-strength-analyser/password-strength-analyser.vue')['default']

10935
pnpm-lock.yaml generated

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,70 @@
// aspect-ratio-calculator.service.test.ts
import { describe, expect, it } from 'vitest';
import {
type AspectRatio,
calculateAspectRatio,
calculateDimensions,
simplifyRatio,
} from './aspect-ratio-calculator.service';
describe('Aspect Ratio Calculator Service', () => {
describe('calculateAspectRatio', () => {
it('calculates correct aspect ratio for 1920x1080', () => {
const result = calculateAspectRatio(1920, 1080);
expect(result).toEqual({ r1: 16, r2: 9 });
});
it('calculates correct aspect ratio for 640x480', () => {
const result = calculateAspectRatio(640, 480);
expect(result).toEqual({ r1: 4, r2: 3 });
});
it('handles square aspect ratio', () => {
const result = calculateAspectRatio(1000, 1000);
expect(result).toEqual({ r1: 1, r2: 1 });
});
});
describe('calculateDimensions', () => {
it('calculates correct height given width and 16:9 ratio', () => {
const ratio: AspectRatio = { r1: 16, r2: 9 };
const result = calculateDimensions(1920, ratio, true);
expect(result).toEqual({ width: 1920, height: 1080 });
});
it('calculates correct width given height and 4:3 ratio', () => {
const ratio: AspectRatio = { r1: 4, r2: 3 };
const result = calculateDimensions(480, ratio, false);
expect(result).toEqual({ width: 640, height: 480 });
});
it('handles 1:1 ratio', () => {
const ratio: AspectRatio = { r1: 1, r2: 1 };
const result = calculateDimensions(500, ratio, true);
expect(result).toEqual({ width: 500, height: 500 });
});
});
describe('simplifyRatio', () => {
it('simplifies 16:9 ratio', () => {
const result = simplifyRatio(16, 9);
expect(result).toEqual({ r1: 16, r2: 9 });
});
it('simplifies 1920:1080 to 16:9', () => {
const result = simplifyRatio(1920, 1080);
expect(result).toEqual({ r1: 16, r2: 9 });
});
it('simplifies 4:2 to 2:1', () => {
const result = simplifyRatio(4, 2);
expect(result).toEqual({ r1: 2, r2: 1 });
});
it('handles already simplified ratios', () => {
const result = simplifyRatio(7, 5);
expect(result).toEqual({ r1: 7, r2: 5 });
});
});
});

View file

@ -0,0 +1,44 @@
// aspect-ratio-calculator.service.ts
export interface AspectRatio {
r1: number
r2: number
}
export interface Dimensions {
width: number
height: number
}
export function calculateAspectRatio(width: number, height: number): AspectRatio {
const gcd = (a: number, b: number): number => (b === 0 ? a : gcd(b, a % b));
const divisor = gcd(width, height);
return {
r1: width / divisor,
r2: height / divisor,
};
}
export function calculateDimensions(
knownDimension: number,
ratio: AspectRatio,
isWidth: boolean,
): Dimensions {
if (isWidth) {
const height = Math.round((knownDimension * ratio.r2) / ratio.r1);
return { width: knownDimension, height };
}
else {
const width = Math.round((knownDimension * ratio.r1) / ratio.r2);
return { width, height: knownDimension };
}
}
export function simplifyRatio(r1: number, r2: number): AspectRatio {
const gcd = (a: number, b: number): number => (b === 0 ? a : gcd(b, a % b));
const divisor = gcd(r1, r2);
return {
r1: r1 / divisor,
r2: r2 / divisor,
};
}

View file

@ -0,0 +1,147 @@
<!-- AspectRatioCalculator.vue -->
<script setup lang="ts">
import { ref } from 'vue';
import { NButton, NInputNumber, NRadio, NRadioGroup, NSpace } from 'naive-ui';
import {
calculateAspectRatio,
calculateDimensions,
} from './aspect-ratio-calculator.service';
const width = ref<number | null>(null);
const height = ref<number | null>(null);
const r1 = ref<number | null>(null);
const r2 = ref<number | null>(null);
const mode = ref<'ratio' | 'dimensions'>('ratio');
const result = ref<string | null>(null);
function calculateResult() {
if (mode.value === 'ratio' && width.value && height.value) {
const ratio = calculateAspectRatio(width.value, height.value);
r1.value = ratio.r1;
r2.value = ratio.r2;
result.value = `Aspect Ratio: ${ratio.r1}:${ratio.r2}`;
}
else if (mode.value === 'dimensions' && r1.value && r2.value) {
if (width.value) {
const dimensions = calculateDimensions(width.value, { r1: r1.value, r2: r2.value }, true);
height.value = dimensions.height;
result.value = `Dimensions: ${dimensions.width}x${dimensions.height}`;
}
else if (height.value) {
const dimensions = calculateDimensions(height.value, { r1: r1.value, r2: r2.value }, false);
width.value = dimensions.width;
result.value = `Dimensions: ${dimensions.width}x${dimensions.height}`;
}
else {
result.value = 'Please enter either width or height to calculate dimensions';
}
}
else {
result.value = 'Please fill in the required fields';
}
}
function clearAll() {
width.value = null;
height.value = null;
r1.value = null;
r2.value = null;
result.value = null;
}
</script>
<template>
<NSpace vertical :size="24">
<NRadioGroup v-model:value="mode">
<NRadio value="ratio">
Calculate Aspect Ratio
</NRadio>
<NRadio value="dimensions">
Calculate Dimensions
</NRadio>
</NRadioGroup>
<div class="input-group">
<div class="input-pair">
<label>Pixels width</label>
<NInputNumber v-model:value="width" placeholder="Pixels width" :min="1" />
</div>
<div class="input-pair">
<label>Pixels height</label>
<NInputNumber v-model:value="height" placeholder="Pixels height" :min="1" />
</div>
</div>
<div class="input-group">
<div class="input-pair">
<label>Ratio width</label>
<NInputNumber v-model:value="r1" placeholder="Ratio width" :min="1" />
</div>
<div class="separator">
:
</div>
<div class="input-pair">
<label>Ratio height</label>
<NInputNumber v-model:value="r2" placeholder="Ratio height" :min="1" />
</div>
</div>
<div class="button-container">
<NButton type="primary" @click="calculateResult">
Calculate
</NButton>
<NButton @click="clearAll">
Clear All
</NButton>
</div>
<div v-if="result" class="result">
{{ result }}
</div>
</NSpace>
</template>
<style scoped>
.input-group {
display: flex;
align-items: flex-end;
gap: 16px;
}
.input-pair {
flex: 1;
display: flex;
flex-direction: column;
}
.input-pair label {
margin-bottom: 4px;
}
.separator {
align-self: flex-end;
margin-bottom: 7px;
font-size: 24px;
font-weight: bold;
}
.button-container {
display: flex;
justify-content: space-between;
gap: 16px;
}
.button-container .n-button {
flex: 1;
}
.result {
font-size: 18px;
font-weight: bold;
text-align: center;
}
:deep(.n-input-number) {
width: 100%;
}
</style>

View file

@ -0,0 +1,12 @@
import { AspectRatio } from '@vicons/tabler';
import { defineTool } from '../tool';
export const tool = defineTool({
name: 'Aspect Ratio Calculator',
path: '/aspect-ratio-calculator',
description: 'Use this ratio calculator to check the dimensions when resizing images.',
keywords: ['aspect', 'ratio', 'calculator'],
component: () => import('./aspect-ratio-calculator.vue'),
icon: AspectRatio,
createdAt: new Date('2024-08-14'),
});

View file

@ -1,6 +1,7 @@
import { tool as base64FileConverter } from './base64-file-converter';
import { tool as base64StringConverter } from './base64-string-converter';
import { tool as basicAuthGenerator } from './basic-auth-generator';
import { tool as aspectRatioCalculator } from './aspect-ratio-calculator';
import { tool as asciiTextDrawer } from './ascii-text-drawer';
@ -136,7 +137,7 @@ export const toolsByCategory: ToolCategory[] = [
},
{
name: 'Images and videos',
components: [qrCodeGenerator, wifiQrCodeGenerator, svgPlaceholderGenerator, cameraRecorder],
components: [qrCodeGenerator, wifiQrCodeGenerator, svgPlaceholderGenerator, cameraRecorder, aspectRatioCalculator],
},
{
name: 'Development',