chore: move all components into Components dir with pascal case (#934)
This commit is contained in:
@@ -0,0 +1,75 @@
|
||||
import { displayStringForContentType, SNComponent } from '@standardnotes/snjs';
|
||||
import { Button } from '@/components/Button';
|
||||
import { FunctionComponent } from 'preact';
|
||||
import { Title, Text, Subtitle, PreferencesSegment } from '../../components';
|
||||
|
||||
export const ConfirmCustomExtension: FunctionComponent<{
|
||||
component: SNComponent;
|
||||
callback: (confirmed: boolean) => void;
|
||||
}> = ({ component, callback }) => {
|
||||
const fields = [
|
||||
{
|
||||
label: 'Name',
|
||||
value: component.package_info.name,
|
||||
},
|
||||
{
|
||||
label: 'Description',
|
||||
value: component.package_info.description,
|
||||
},
|
||||
{
|
||||
label: 'Version',
|
||||
value: component.package_info.version,
|
||||
},
|
||||
{
|
||||
label: 'Hosted URL',
|
||||
value: component.thirdPartyPackageInfo.url,
|
||||
},
|
||||
{
|
||||
label: 'Download URL',
|
||||
value: component.package_info.download_url,
|
||||
},
|
||||
{
|
||||
label: 'Extension Type',
|
||||
value: displayStringForContentType(component.content_type),
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<PreferencesSegment>
|
||||
<Title>Confirm Extension</Title>
|
||||
|
||||
{fields.map((field) => {
|
||||
if (!field.value) {
|
||||
return undefined;
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<Subtitle>{field.label}</Subtitle>
|
||||
<Text className={'wrap'}>{field.value}</Text>
|
||||
<div className="min-h-2" />
|
||||
</>
|
||||
);
|
||||
})}
|
||||
|
||||
<div className="min-h-3" />
|
||||
|
||||
<div className="flex flex-row">
|
||||
<Button
|
||||
className="min-w-20"
|
||||
type="normal"
|
||||
label="Cancel"
|
||||
onClick={() => callback(false)}
|
||||
/>
|
||||
|
||||
<div className="min-w-3" />
|
||||
|
||||
<Button
|
||||
className="min-w-20"
|
||||
type="normal"
|
||||
label="Install"
|
||||
onClick={() => callback(true)}
|
||||
/>
|
||||
</div>
|
||||
</PreferencesSegment>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,115 @@
|
||||
import { FunctionComponent } from 'preact';
|
||||
import { SNComponent } from '@standardnotes/snjs';
|
||||
import {
|
||||
PreferencesSegment,
|
||||
SubtitleLight,
|
||||
Title,
|
||||
} from '@/components/Preferences/components';
|
||||
import { Switch } from '@/components/Switch';
|
||||
import { WebApplication } from '@/ui_models/application';
|
||||
import { useState } from 'preact/hooks';
|
||||
import { Button } from '@/components/Button';
|
||||
import { RenameExtension } from './RenameExtension';
|
||||
|
||||
const UseHosted: FunctionComponent<{
|
||||
offlineOnly: boolean;
|
||||
toggleOfllineOnly: () => void;
|
||||
}> = ({ offlineOnly, toggleOfllineOnly }) => (
|
||||
<div className="flex flex-row">
|
||||
<SubtitleLight className="flex-grow">
|
||||
Use hosted when local is unavailable
|
||||
</SubtitleLight>
|
||||
<Switch onChange={toggleOfllineOnly} checked={!offlineOnly} />
|
||||
</div>
|
||||
);
|
||||
|
||||
export interface ExtensionItemProps {
|
||||
application: WebApplication;
|
||||
extension: SNComponent;
|
||||
first: boolean;
|
||||
latestVersion: string | undefined;
|
||||
uninstall: (extension: SNComponent) => void;
|
||||
toggleActivate?: (extension: SNComponent) => void;
|
||||
}
|
||||
|
||||
export const ExtensionItem: FunctionComponent<ExtensionItemProps> = ({
|
||||
application,
|
||||
extension,
|
||||
first,
|
||||
uninstall,
|
||||
}) => {
|
||||
const [offlineOnly, setOfflineOnly] = useState(
|
||||
extension.offlineOnly ?? false
|
||||
);
|
||||
const [extensionName, setExtensionName] = useState(extension.name);
|
||||
|
||||
const toggleOffllineOnly = () => {
|
||||
const newOfflineOnly = !offlineOnly;
|
||||
setOfflineOnly(newOfflineOnly);
|
||||
application
|
||||
.changeAndSaveItem(extension.uuid, (m: any) => {
|
||||
if (m.content == undefined) m.content = {};
|
||||
m.content.offlineOnly = newOfflineOnly;
|
||||
})
|
||||
.then((item) => {
|
||||
const component = item as SNComponent;
|
||||
setOfflineOnly(component.offlineOnly);
|
||||
})
|
||||
.catch((e) => {
|
||||
console.error(e);
|
||||
});
|
||||
};
|
||||
|
||||
const changeExtensionName = (newName: string) => {
|
||||
setExtensionName(newName);
|
||||
application
|
||||
.changeAndSaveItem(extension.uuid, (m: any) => {
|
||||
if (m.content == undefined) m.content = {};
|
||||
m.content.name = newName;
|
||||
})
|
||||
.then((item) => {
|
||||
const component = item as SNComponent;
|
||||
setExtensionName(component.name);
|
||||
});
|
||||
};
|
||||
|
||||
const localInstallable = extension.package_info.download_url;
|
||||
const isThirParty = application.features.isThirdPartyFeature(
|
||||
extension.identifier
|
||||
);
|
||||
|
||||
return (
|
||||
<PreferencesSegment classes={'mb-5'}>
|
||||
{first && (
|
||||
<>
|
||||
<Title>Extensions</Title>
|
||||
</>
|
||||
)}
|
||||
|
||||
<RenameExtension
|
||||
extensionName={extensionName}
|
||||
changeName={changeExtensionName}
|
||||
/>
|
||||
<div className="min-h-2" />
|
||||
|
||||
{isThirParty && localInstallable && (
|
||||
<UseHosted
|
||||
offlineOnly={offlineOnly}
|
||||
toggleOfllineOnly={toggleOffllineOnly}
|
||||
/>
|
||||
)}
|
||||
|
||||
<>
|
||||
<div className="min-h-2" />
|
||||
<div className="flex flex-row">
|
||||
<Button
|
||||
className="min-w-20"
|
||||
type="normal"
|
||||
label="Uninstall"
|
||||
onClick={() => uninstall(extension)}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
</PreferencesSegment>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,52 @@
|
||||
import { WebApplication } from '@/ui_models/application';
|
||||
import { FeatureDescription } from '@standardnotes/features';
|
||||
import { SNComponent, ClientDisplayableError } from '@standardnotes/snjs';
|
||||
import { makeAutoObservable, observable } from 'mobx';
|
||||
|
||||
export class ExtensionsLatestVersions {
|
||||
static async load(
|
||||
application: WebApplication
|
||||
): Promise<ExtensionsLatestVersions | undefined> {
|
||||
const response = await application.getAvailableSubscriptions();
|
||||
|
||||
if (response instanceof ClientDisplayableError) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const versionMap: Map<string, string> = new Map();
|
||||
collectFeatures(
|
||||
response.CORE_PLAN?.features as FeatureDescription[],
|
||||
versionMap
|
||||
);
|
||||
collectFeatures(
|
||||
response.PLUS_PLAN?.features as FeatureDescription[],
|
||||
versionMap
|
||||
);
|
||||
collectFeatures(
|
||||
response.PRO_PLAN?.features as FeatureDescription[],
|
||||
versionMap
|
||||
);
|
||||
|
||||
return new ExtensionsLatestVersions(versionMap);
|
||||
}
|
||||
|
||||
constructor(private readonly latestVersionsMap: Map<string, string>) {
|
||||
makeAutoObservable<ExtensionsLatestVersions, 'latestVersionsMap'>(this, {
|
||||
latestVersionsMap: observable.ref,
|
||||
});
|
||||
}
|
||||
|
||||
getVersion(extension: SNComponent): string | undefined {
|
||||
return this.latestVersionsMap.get(extension.package_info.identifier);
|
||||
}
|
||||
}
|
||||
|
||||
function collectFeatures(
|
||||
features: FeatureDescription[] | undefined,
|
||||
versionMap: Map<string, string>
|
||||
) {
|
||||
if (features == undefined) return;
|
||||
for (const feature of features) {
|
||||
versionMap.set(feature.identifier, feature.version!);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
import { FunctionComponent } from 'preact';
|
||||
import { useState, useRef, useEffect } from 'preact/hooks';
|
||||
|
||||
export const RenameExtension: FunctionComponent<{
|
||||
extensionName: string;
|
||||
changeName: (newName: string) => void;
|
||||
}> = ({ extensionName, changeName }) => {
|
||||
const [isRenaming, setIsRenaming] = useState(false);
|
||||
const [newExtensionName, setNewExtensionName] =
|
||||
useState<string>(extensionName);
|
||||
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (isRenaming) {
|
||||
inputRef.current!.focus();
|
||||
}
|
||||
}, [inputRef, isRenaming]);
|
||||
|
||||
const startRenaming = () => {
|
||||
setNewExtensionName(extensionName);
|
||||
setIsRenaming(true);
|
||||
};
|
||||
|
||||
const cancelRename = () => {
|
||||
setNewExtensionName(extensionName);
|
||||
setIsRenaming(false);
|
||||
};
|
||||
|
||||
const confirmRename = () => {
|
||||
if (!newExtensionName) {
|
||||
return;
|
||||
}
|
||||
changeName(newExtensionName);
|
||||
setIsRenaming(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-row mr-3 items-center">
|
||||
<input
|
||||
ref={inputRef}
|
||||
disabled={!isRenaming}
|
||||
autocomplete="off"
|
||||
className="flex-grow text-base font-bold no-border bg-default px-0 color-text"
|
||||
type="text"
|
||||
value={newExtensionName}
|
||||
onChange={({ target: input }) =>
|
||||
setNewExtensionName((input as HTMLInputElement)?.value)
|
||||
}
|
||||
/>
|
||||
<div className="min-w-3" />
|
||||
{isRenaming ? (
|
||||
<>
|
||||
<a className="pt-1 cursor-pointer" onClick={confirmRename}>
|
||||
Confirm
|
||||
</a>
|
||||
<div className="min-w-3" />
|
||||
<a className="pt-1 cursor-pointer" onClick={cancelRename}>
|
||||
Cancel
|
||||
</a>
|
||||
</>
|
||||
) : (
|
||||
<a className="pt-1 cursor-pointer" onClick={startRenaming}>
|
||||
Rename
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,3 @@
|
||||
export * from './ConfirmCustomExtension';
|
||||
export * from './ExtensionItem';
|
||||
export * from './ExtensionsLatestVersions';
|
||||
Reference in New Issue
Block a user