Commit 388aa443 authored by Дмитрий Малюгин's avatar Дмитрий Малюгин 🕓
Browse files

feat: init 'Cropper'

parent 6480193c
Loading
Loading
Loading
Loading
+7 −1
Original line number Diff line number Diff line
@@ -183,7 +183,13 @@ export interface IPopupProps {
}

export interface ICropperProps {
  size?: TSize;
  src?: string;
  file?: File;
  width?: number;
  height?: number;
  menuPosition?: 'top' | 'right' | 'bottom' | 'left';
  theme?: TThemeColor;
  darknessTheme?: TDarkness;
}

export interface IColorPickerProps {
+7 −4
Original line number Diff line number Diff line
@@ -14,10 +14,11 @@ const meta: Meta = {
    },
  },
  argTypes: {
    buttonProps: { control: 'object' },
    sameButtonColor: { control: 'boolean' },
    menuPosition: { control: 'select', options: ['top', 'right', 'bottom', 'left'] },
    src: { control: 'text' },
    width: { control: 'number' },
    height: { control: 'number' },
    disabled: { control: 'boolean' },
    size: { control: 'select', options: ['small', 'normal', 'large', 'huge'] },
  },
} satisfies Meta<typeof Cropper>;

@@ -26,7 +27,9 @@ export default meta;
type Story = StoryObj<typeof meta>;

export const Simple: Story = {
  args: {},
  args: {
    src: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQoFRQjM-wM_nXMA03AGDXgJK3VeX7vtD3ctA&s',
  },
};

export const Full: Story = {
+105 −3
Original line number Diff line number Diff line
<script setup lang="ts">
import type { ICropperProps } from '@interfaces/componentsProps';
import { computed, ref, watch } from 'vue';
import Button from '@components/Button/Button.vue';
import { convertThemeToTextColor } from '@helpers/common';
import SaveIcon from '@icons/Mono/SaveIcon.vue';
import CornerLeftTopIcon from '@icons/Mono/CornerLeftTopIcon.vue';

const props = withDefaults(defineProps<ICropperProps>(), {
  size: 'normal',
  disabled: false,
  width: 300,
  height: 300,
  menuPosition: 'top',
  theme: 'black',
  darknessTheme: '500',
});

const canvas = ref();

const ctx = computed(() => canvas.value && canvas.value.getContext('2d'));
const imageSource = computed(() => props.src ?? props.file);
const width = computed(() => props.width);
const height = computed(() => props.height);
const color = computed(() => convertThemeToTextColor(props.theme, props.darknessTheme));

watch(
  [imageSource, ctx, width, height],
  () => {
    if (!imageSource.value) return;

    const img = new Image();
    img.src = props.src ?? URL.createObjectURL(props.file);

    img.onload = () => {
      canvas.value.width = width.value ?? 0;
      canvas.value.height = height.value ?? 0;
      ctx.value?.drawImage(img, 0, 0, width.value ?? 0, height.value ?? 0);
    };
  },
  { immediate: true },
);
</script>

<template>
  <section class="container"></section>
  <section
    :class="[
      'container',
      {
        flexVertical: menuPosition === 'top' || menuPosition === 'bottom',
      },
    ]"
  >
    <div class="canvas-container">
      <canvas ref="canvas" id="cropper-canvas"> </canvas>
      <div class="crop-border left top">
        <CornerLeftTopIcon size="16" />
      </div>
      <div class="crop-border right top"></div>
      <div class="crop-border left bottom"></div>
      <div class="crop-border right bottom"></div>
    </div>
    <div
      v-show="imageSource"
      :class="[
        'buttons',
        {
          order1: menuPosition === 'top' || menuPosition === 'left',
          flexVertical: menuPosition === 'right' || menuPosition === 'left',
        },
      ]"
    >
      <Button :theme="theme" :darknessTheme="darknessTheme" label="Reset" />
      <Button :theme="theme" :darknessTheme="darknessTheme" label="Save">
        <SaveIcon :color="color" size="16" />
      </Button>
    </div>
  </section>
</template>

<style scoped>
.container {
  display: flex;
  align-items: center;
  width: max-content;
}
.canvas-container {
  position: relative;
  line-height: 0;
}
#cropper-canvas {
  border: 1px solid black;
}
.buttons {
  display: flex;
  align-items: center;
  gap: 20px;
}
.crop-border {
  position: absolute;
  z-index: 50;
  width: 20px;
  height: 20px;
  background-color: gray;
}
.top {
  top: 0;
}
.right {
  right: 0;
}
.bottom {
  bottom: 0;
}
.flexVertical {
  flex-direction: column;
}
.order1 {
  order: -1;
}
</style>
+7 −0
Original line number Diff line number Diff line
export const getImageInfo = (image: string | File, instance: HTMLImageElement) => {
  instance.src = typeof image === 'string' ? image : URL.createObjectURL(image);

  instance.onload = function () {
    return [instance, 0, 0, instance.width, instance.height];
  };
};
+26 −0
Original line number Diff line number Diff line
<script setup lang="ts">
interface Props {
  color?: string;
  size?: string | number;
}
defineProps<Props>();
</script>

<template>
  <svg
    :width="`${size ?? 40}px`"
    :height="`${size ?? 40}px`"
    viewBox="3 3 10 10"
    fill="none"
    xmlns="http://www.w3.org/2000/svg"
  >
    <path
      fill-rule="evenodd"
      clip-rule="evenodd"
      d="M9.8774 3c-1.101 0-1.9579 0-2.6453.0562-.6979.057-1.265.1744-1.7751.4343-.8467.4314-1.5351 1.1198-1.9665 1.9665-.2599.5101-.3773 1.0772-.4343 1.7751C3 7.9195 3 9 3 10V9.9v2.1c0 .2761.2239.5.5.5s.5-.2239.5-.5V9.9c0-1.1284.0004-1.9446.0528-2.5865.052-.6361.1526-1.0569.3287-1.4025.3355-.6585.871-1.194 1.5295-1.5295.3456-.1761.7664-.2767 1.4025-.3287C7.9554 4.0004 8.7716 4 9.9 4h2.1c.2761 0 .5-.2239.5-.5s-.2239-.5-.5-.5H9.9 9.8774Z"
      :fill="color ?? '#000000'"
    />
  </svg>
</template>

<style scoped></style>