Skip to content

Commit 95b8a67

Browse files
author
ricwilson
committed
feat: update ImageCrop control to version 0.0.6 with new default crop properties and action output schema
1 parent 2b89c33 commit 95b8a67

File tree

10 files changed

+130
-11
lines changed

10 files changed

+130
-11
lines changed

ImageCrop/ImageCrop/ControlManifest.Input.xml

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<?xml version="1.0" encoding="utf-8"?>
22
<manifest>
3-
<control namespace="RAW" constructor="ImageCrop" version="0.0.5" display-name-key="ImageCrop_DisplayName" description-key="ImageCrop_Description" control-type="standard">
3+
<control namespace="RAW" constructor="ImageCrop" version="0.0.6" display-name-key="ImageCrop_DisplayName" description-key="ImageCrop_Description" control-type="standard">
44
<!--external-service-usage node declares whether this 3rd party PCF control is using external service or not, if yes, this control will be considered as premium and please also add the external domain it is using.
55
If it is not using any external service, please set the enabled="false" and DO NOT add any domain below. The "enabled" will be false by default.
66
Example1:
@@ -32,6 +32,19 @@
3232
<property name="locked" display-name-key="ImageCrop_Locked_DisplayName" description-key="ImageCrop_Locked_Description" of-type="TwoOptions" usage="input" required="false" default-value="false" />
3333
<property name="ruleOfThirds" display-name-key="ImageCrop_RuleOfThirds_DisplayName" description-key="ImageCrop_RuleOfThirds_Description" of-type="TwoOptions" usage="input" required="false" default-value="false" />
3434
<property name="circularCrop" display-name-key="ImageCrop_CircularCrop_DisplayName" description-key="ImageCrop_CircularCrop_Description" of-type="TwoOptions" usage="input" required="false" default-value="false" />
35+
<property name="DefaultUnit" display-name-key="ImageCrop_DefaultUnit_DisplayName" description-key="ImageCrop_DefaultUnit_Description" of-type="Enum" usage="input" required="false" default-value="%">
36+
<value name="px" display-name-key="ImageCrop_DefaultUnit_Enum_px" description-key="ImageCrop_DefaultUnit_Enum_px_Description">px</value>
37+
<value name="%" display-name-key="ImageCrop_DefaultUnit_Enum_percent" description-key="ImageCrop_DefaultUnit_Enum_percent_Description">%</value>
38+
</property>
39+
<property name="DefaultX" display-name-key="ImageCrop_DefaultX_DisplayName" description-key="ImageCrop_DefaultX_Description" of-type="Decimal" usage="input" required="false" default-value="-1" />
40+
<property name="DefaultY" display-name-key="ImageCrop_DefaultY_DisplayName" description-key="ImageCrop_DefaultY_Description" of-type="Decimal" usage="input" required="false" default-value="-1" />
41+
<property name="DefaultWidth" display-name-key="ImageCrop_DefaultWidth_DisplayName" description-key="ImageCrop_DefaultWidth_Description" of-type="Decimal" usage="input" required="false" default-value="-1" />
42+
<property name="DefaultHeight" display-name-key="ImageCrop_DefaultHeight_DisplayName" description-key="ImageCrop_DefaultHeight_Description" of-type="Decimal" usage="input" required="false" default-value="-1" />
43+
<property name="actionSchema" display-name-key="ImageCrop_ActionSchema_DisplayName" description-key="ImageCrop_ActionSchema_Description" of-type="SingleLine.Text" usage="input" hidden="true" />
44+
<property name="actionOutput" display-name-key="ImageCrop_ActionOutput_DisplayName" description-key="ImageCrop_ActionOutput_Description" of-type="Object" usage="output" default-value="" />
45+
<property-dependencies>
46+
<property-dependency input="actionSchema" output="actionOutput" required-for="schema" />
47+
</property-dependencies>
3548
<resources>
3649
<code path="index.ts" order="1" />
3750
<!-- UNCOMMENT TO ADD MORE RESOURCES

ImageCrop/ImageCrop/components/imageCropControl.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,14 @@ import {
2020
useRotation,
2121
useScaling
2222
} from "../hooks";
23+
import { useDefaultCrop } from "../hooks/useDefaultCrop";
2324
import CropWrapper from "./imageCropWrapper";
2425

2526
const ImageCropControl: React.FC<IImageCropControlProps> = (props) => {
2627
const pcfContext = usePcfContext();
2728
const [crop, setCrop] = React.useState<Crop>();
29+
// Set crop from manifest defaults on first load (modular)
30+
useDefaultCrop(pcfContext.context, setCrop, crop);
2831
const [completedCrop, setCompletedCrop] = React.useState<PixelCrop>()
2932
const imgRef = React.useRef<HTMLImageElement>(null) as React.RefObject<HTMLImageElement>;
3033
const appScaling = useResponsiveAppScaling(pcfContext.context, imgRef);
@@ -89,7 +92,7 @@ const ImageCropControl: React.FC<IImageCropControlProps> = (props) => {
8992
style={{
9093
maxWidth: '100%',
9194
maxHeight: '100%',
92-
display: imageSrc ? 'block' : 'none',
95+
display: imageSrc && pcfContext.isVisible() ? 'block' : 'none',
9396
transform: `rotate(${rotation}deg) scale(${scaling})`
9497
}}
9598
onError={(e) => {

ImageCrop/ImageCrop/hooks/useCropToBase64.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@ export function useCropToBase64(
55
imgRef: RefObject<HTMLImageElement | null>,
66
completedCrop: PixelCrop | undefined,
77
onCropComplete: (base64: string) => void,
8-
rotation: number = 0,
9-
scaling: number = 1,
10-
circularCrop: boolean = false
8+
rotation = 0,
9+
scaling = 1,
10+
circularCrop = false
1111
) {
1212
useEffect(() => {
1313
if (!completedCrop || !imgRef.current) return;
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { Crop } from "react-image-crop";
2+
import { IInputs } from "../generated/ManifestTypes";
3+
import { useRef, useEffect } from "react";
4+
5+
export function useDefaultCrop(
6+
context: ComponentFramework.Context<IInputs>,
7+
setCrop: (crop: Crop) => void,
8+
crop: Crop | undefined
9+
) {
10+
const didSet = useRef(false);
11+
12+
useEffect(() => {
13+
if (didSet.current) return;
14+
const unit = context.parameters.DefaultUnit.raw || "%";
15+
const x = context.parameters.DefaultX.raw ?? -1;
16+
const y = context.parameters.DefaultY.raw ?? -1;
17+
const width = context.parameters.DefaultWidth.raw ?? -1;
18+
const height = context.parameters.DefaultHeight.raw ?? -1;
19+
if (x !== -1 && y !== -1 && width !== -1 && height !== -1 && !crop) {
20+
setCrop({ unit, x, y, width, height });
21+
didSet.current = true;
22+
}
23+
}, [context, setCrop, crop]);
24+
}

ImageCrop/ImageCrop/hooks/useResponsiveAppScaling.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
import { useState, useEffect } from "react";
2+
import { IInputs } from "../generated/ManifestTypes";
23

34
/**
45
* Calculates the scaling factor applied by Power Apps' responsive layout.
56
* Compares allocated size (from context) to rendered size (via getBoundingClientRect).
67
*/
78
export function useResponsiveAppScaling(
8-
context: ComponentFramework.Context<any>,
9+
context: ComponentFramework.Context<IInputs>,
910
elementRef: React.RefObject<HTMLElement | null>
1011
): number {
1112
const [appScaling, setAppScaling] = useState(1);

ImageCrop/ImageCrop/index.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import * as ReactDOM from "react-dom/client";
44
import { PercentCrop, PixelCrop } from "react-image-crop";
55
import { v4 as uuidv4 } from "uuid";
66
import ImageCropApp from "./imageCropApp";
7+
import { ActionOutputSchema } from "./types/actionOutputSchema";
78

89
export class ImageCrop implements ComponentFramework.StandardControl<IInputs, IOutputs> {
910

@@ -51,6 +52,12 @@ export class ImageCrop implements ComponentFramework.StandardControl<IInputs, IO
5152
this._reactRoot = ReactDOM.createRoot(this._container);
5253
}
5354

55+
public async getOutputSchema(context: ComponentFramework.Context<IInputs>): Promise<Record<string, unknown>> {
56+
return Promise.resolve({
57+
Data: ActionOutputSchema
58+
});
59+
}
60+
5461
/**
5562
* Called when any value in the property bag has changed. This includes field values, data-sets, global values such as container height and width, offline status, control metadata values such as label, visible, etc.
5663
* @param context The entire property bag available to control via Context Object; It contains values as set up by the customizer mapped to names defined in the manifest, as well as utility functions
@@ -81,7 +88,7 @@ export class ImageCrop implements ComponentFramework.StandardControl<IInputs, IO
8188
};
8289

8390
// Callback for drag start
84-
public onDragStart = (e: PointerEvent) => {
91+
public onDragStart = (e: PointerEvent) => {
8592
this._actionDragStart = true;
8693
this._notifyOutputChanged();
8794
};
@@ -100,7 +107,11 @@ export class ImageCrop implements ComponentFramework.StandardControl<IInputs, IO
100107
this._updateFromOutput = true;
101108
let notifyAgain = false;
102109

103-
const output: IOutputs = {}
110+
const output: IOutputs = {
111+
actionOutput: {
112+
action: "TEST"
113+
}
114+
};
104115

105116
if (this._actionCropComplete) {
106117
notifyAgain = true;

ImageCrop/ImageCrop/resources/ImageCrop.1033.resx

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,4 +104,62 @@
104104
<data name="ImageCrop_Description" xml:space="preserve">
105105
<value>A simple Power Apps PCF control for cropping images and returning the cropped image as base64.</value>
106106
</data>
107-
</root>
107+
<data name="ImageCrop_ActionOutput_DisplayName" xml:space="preserve">
108+
<value>Action Output</value>
109+
</data>
110+
<data name="ImageCrop_ActionOutput_Description" xml:space="preserve">
111+
<value>The most recent action and its data, output as an object.</value>
112+
</data>
113+
<data name="ImageCrop_ActionSchema_DisplayName" xml:space="preserve">
114+
<value>Action Schema</value>
115+
</data>
116+
<data name="ImageCrop_ActionSchema_Description" xml:space="preserve">
117+
<value>Defines the schema for the action output object (internal use).</value>
118+
</data>
119+
<!-- Default Crop Properties Display Names -->
120+
<data name="ImageCrop_DefaultUnit_DisplayName" xml:space="preserve">
121+
<value>Default Crop Unit</value>
122+
</data>
123+
<data name="ImageCrop_DefaultX_DisplayName" xml:space="preserve">
124+
<value>Default Crop X</value>
125+
</data>
126+
<data name="ImageCrop_DefaultY_DisplayName" xml:space="preserve">
127+
<value>Default Crop Y</value>
128+
</data>
129+
<data name="ImageCrop_DefaultWidth_DisplayName" xml:space="preserve">
130+
<value>Default Crop Width</value>
131+
</data>
132+
<data name="ImageCrop_DefaultHeight_DisplayName" xml:space="preserve">
133+
<value>Default Crop Height</value>
134+
</data>
135+
<!-- DefaultUnit Enum Display Names -->
136+
<data name="ImageCrop_DefaultUnit_Enum_px" xml:space="preserve">
137+
<value>Pixels (px)</value>
138+
</data>
139+
<data name="ImageCrop_DefaultUnit_Enum_percent" xml:space="preserve">
140+
<value>Percent (%)</value>
141+
</data>
142+
<!-- Default Crop Properties Descriptions -->
143+
<data name="ImageCrop_DefaultUnit_Description" xml:space="preserve">
144+
<value>The unit for the default crop (px or %).</value>
145+
</data>
146+
<data name="ImageCrop_DefaultX_Description" xml:space="preserve">
147+
<value>The default X position for the crop. Set to -1 to ignore.</value>
148+
</data>
149+
<data name="ImageCrop_DefaultY_Description" xml:space="preserve">
150+
<value>The default Y position for the crop. Set to -1 to ignore.</value>
151+
</data>
152+
<data name="ImageCrop_DefaultWidth_Description" xml:space="preserve">
153+
<value>The default width for the crop. Set to -1 to ignore.</value>
154+
</data>
155+
<data name="ImageCrop_DefaultHeight_Description" xml:space="preserve">
156+
<value>The default height for the crop. Set to -1 to ignore.</value>
157+
</data>
158+
<!-- DefaultUnit Enum Descriptions -->
159+
<data name="ImageCrop_DefaultUnit_Enum_px_Description" xml:space="preserve">
160+
<value>Use pixels for the crop unit.</value>
161+
</data>
162+
<data name="ImageCrop_DefaultUnit_Enum_percent_Description" xml:space="preserve">
163+
<value>Use percent for the crop unit.</value>
164+
</data>
165+
</root>
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
export const ActionOutputSchema = {
2+
$schema: "http://json-schema.org/draft-04/schema#",
3+
type: "object",
4+
properties: {
5+
action:{
6+
type: "string"
7+
}
8+
}
9+
};

ImageCrop/ImageCrop/utils/cropUtils.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ export function makeAspectCrop(
88
): Crop {
99
if (!aspect || !mediaWidth || !mediaHeight) return crop as Crop;
1010
let width = crop.width ?? 100;
11-
let unit = crop.unit ?? "%";
11+
const unit = crop.unit ?? "%";
1212
let height = width / aspect;
1313
if (unit === "%") {
1414
width = (mediaWidth * width) / 100;

ImageCrop/Solution/RAW!ImageCropControl/src/Other/Solution.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
<LocalizedName description="RAW!ImageCropControl" languagecode="1033" />
99
</LocalizedNames>
1010
<Descriptions />
11-
<Version>1.0.5</Version>
11+
<Version>1.0.6</Version>
1212
<!-- Solution Package Type: Unmanaged(0)/Managed(1)/Both(2)-->
1313
<Managed>2</Managed>
1414
<Publisher>

0 commit comments

Comments
 (0)