Repository: GraphiteEditor/Graphite
File: frontend/src/components/widgets/inputs/ColorInput.vue (Vue)

Code before:
	props: {
		value: { type: String as PropType<string | undefined>, required: false },
		label: { type: String as PropType<string>, required: false },
		noTransparency: { type: Boolean as PropType<boolean>, default: false },
		disabled: { type: Boolean as PropType<boolean>, default: false },
		tooltip: { type: String as PropType<string | undefined>, required: false },

		// Bound through `v-model`
		// TODO: See if this should be made to follow the pattern of DropdownInput.vue so this could be removed
		open: { type: Boolean as PropType<boolean>, required: true },
	},
	data() {
		return {
			isOpen: false,
		};
	},
	computed: {
		color(): Color {
			if (!this.value) return new Color(0, 0, 0, 1);

			const r = parseInt(this.value.slice(0, 2), 16) / 255;
			const g = parseInt(this.value.slice(2, 4), 16) / 255;
			const b = parseInt(this.value.slice(4, 6), 16) / 255;
			const a = parseInt(this.value.slice(6, 8), 16) / 255;

			return new Color(r, g, b, a);
		},
		displayValue(): string {
			if (!this.value) return "";

			const value = this.value.toLowerCase();
			const shortenedIfOpaque = value.slice(-2) === "ff" ? value.slice(0, 6) : value;
			return `#${shortenedIfOpaque}`;
		},
	},
	watch: {
		// Called only when `open` is changed from outside this component (with v-model)
		open(newOpen: boolean) {
			this.isOpen = newOpen;
		},
		isOpen(newIsOpen: boolean) {
			this.$emit("update:open", newIsOpen);
		},
	},
	methods: {
		colorPickerUpdated(color: Color) {
			const twoDigitHex = (value: number): string =>
				Math.floor(value * 255)
					.toString(16)
					.padStart(2, "0");
			const newValue = `${twoDigitHex(color.red)}${twoDigitHex(color.green)}${twoDigitHex(color.blue)}${twoDigitHex(color.alpha)}`;

Review comment (bug):
don't know if this is cleaner but you could do

```ts
const [r, g, b, a] = this.value.match(/.{1,2}/g).map(byte => parseInt(byte, 16))
```
the match(/.{1,2}/g) chunks the string into pairs https://stackoverflow.com/questions/7033639/split-large-string-in-n-size-chunks-in-javascript

You also have to make the if statement above check for non conforming lengths

Code after:
	props: {
		value: { type: String as PropType<string | undefined>, required: false },
		label: { type: String as PropType<string>, required: false },
		noTransparency: { type: Boolean as PropType<boolean>, default: false },
		disabled: { type: Boolean as PropType<boolean>, default: false },
		tooltip: { type: String as PropType<string | undefined>, required: false },

		// Bound through `v-model`
		// TODO: See if this should be made to follow the pattern of DropdownInput.vue so this could be removed
		open: { type: Boolean as PropType<boolean>, required: true },
	},
	data() {
		return {
			isOpen: false,
		};
	},
	computed: {
		color(): Color {
			// TODO: Validate length of value, and allow shorthand versions like single-digit or three-digit hex codes
			if (!this.value) return new Color(0, 0, 0, 1);

			const r = parseInt(this.value.slice(0, 2), 16) / 255;
			const g = parseInt(this.value.slice(2, 4), 16) / 255;
			const b = parseInt(this.value.slice(4, 6), 16) / 255;
			const a = parseInt(this.value.slice(6, 8), 16) / 255;

			return new Color(r, g, b, a);
		},
		displayValue(): string {
			if (!this.value) return "";

			const value = this.value.toLowerCase();
			const shortenedIfOpaque = value.slice(-2) === "ff" ? value.slice(0, 6) : value;
			return `#${shortenedIfOpaque}`;
		},
	},
	watch: {
		// Called only when `open` is changed from outside this component (with v-model)
		open(newOpen: boolean) {
			this.isOpen = newOpen;
		},
		isOpen(newIsOpen: boolean) {
			this.$emit("update:open", newIsOpen);
		},
	},
	methods: {
		colorPickerUpdated(color: Color) {
			const twoDigitHex = (value: number): string =>
				Math.floor(value * 255)
					.toString(16)
					.padStart(2, "0");