mirror of
https://github.com/spacedriveapp/spacedrive.git
synced 2026-05-05 13:52:56 -04:00
Merge branch 'main' into 0.5.0-dev
This commit is contained in:
@@ -37,6 +37,7 @@ export default defineConfig(({ mode }) => {
|
||||
plugins: [
|
||||
devtoolsPlugin,
|
||||
process.env.SENTRY_AUTH_TOKEN &&
|
||||
// All this plugin does is give Sentry access to source maps and release data for errors that users *choose* to report
|
||||
sentryVitePlugin({
|
||||
authToken: process.env.SENTRY_AUTH_TOKEN,
|
||||
org: 'spacedriveapp',
|
||||
|
||||
@@ -7,9 +7,8 @@ license.workspace = true
|
||||
repository.workspace = true
|
||||
rust-version.workspace = true
|
||||
|
||||
[target.'cfg(target_os = "android")'.dependencies]
|
||||
sd-core = { default-features = false, features = ["mobile"], path = "../../../../../core" }
|
||||
|
||||
# Spacedrive Sub-crates
|
||||
[target.'cfg(target_os = "ios")'.dependencies]
|
||||
sd-core = { default-features = false, features = [
|
||||
"ffmpeg",
|
||||
@@ -17,6 +16,10 @@ sd-core = { default-features = false, features = [
|
||||
"mobile"
|
||||
], path = "../../../../../core" }
|
||||
|
||||
[target.'cfg(target_os = "android")'.dependencies]
|
||||
sd-core = { path = "../../../../../core", features = ["mobile"], default-features = false }
|
||||
|
||||
[dependencies]
|
||||
# Workspace dependencies
|
||||
futures = { workspace = true }
|
||||
rspc = { workspace = true }
|
||||
|
||||
@@ -20,7 +20,7 @@ import { SafeAreaProvider } from 'react-native-safe-area-context';
|
||||
import { useSnapshot } from 'valtio';
|
||||
import {
|
||||
ClientContextProvider,
|
||||
initPlausible,
|
||||
configureAnalyticsProperties,
|
||||
LibraryContextProvider,
|
||||
P2PContextProvider,
|
||||
RspcProvider,
|
||||
@@ -63,7 +63,7 @@ function AppNavigation() {
|
||||
|
||||
useEffect(() => {
|
||||
if (buildInfo?.data) {
|
||||
initPlausible({ platformType: 'mobile', buildInfo: buildInfo.data });
|
||||
configureAnalyticsProperties({ platformType: 'mobile', buildInfo: buildInfo.data });
|
||||
}
|
||||
}, [buildInfo]);
|
||||
|
||||
|
||||
@@ -37,8 +37,9 @@ export const useContextValue = () => {
|
||||
};
|
||||
|
||||
export const shareTelemetrySchema = z.union([
|
||||
z.literal('share-telemetry'),
|
||||
z.literal('minimal-telemetry')
|
||||
z.literal('full'),
|
||||
z.literal('minimal'),
|
||||
z.literal('none')
|
||||
]);
|
||||
|
||||
const schemas = {
|
||||
@@ -58,7 +59,7 @@ const useFormState = () => {
|
||||
defaultValues: {
|
||||
NewLibrary: obStore.data?.['new-library'] ?? undefined,
|
||||
Privacy: obStore.data?.privacy ?? {
|
||||
shareTelemetry: 'share-telemetry'
|
||||
shareTelemetry: 'full'
|
||||
}
|
||||
},
|
||||
onData: (data) => (onboardingStore.data = data)
|
||||
@@ -81,7 +82,7 @@ const useFormState = () => {
|
||||
|
||||
// opted to place this here as users could change their mind before library creation/onboarding finalization
|
||||
// it feels more fitting to configure it here (once)
|
||||
telemetryState.shareFullTelemetry = data.Privacy.shareTelemetry === 'share-telemetry';
|
||||
telemetryState.telemetryLevelPreference = data.Privacy.shareTelemetry;
|
||||
|
||||
try {
|
||||
// show creation screen for a bit for smoothness
|
||||
@@ -93,7 +94,7 @@ const useFormState = () => {
|
||||
new Promise((res) => setTimeout(res, 500))
|
||||
]);
|
||||
|
||||
if (telemetryState.shareFullTelemetry) {
|
||||
if (telemetryState.telemetryLevelPreference === 'full') {
|
||||
submitPlausibleEvent({ event: { type: 'libraryCreate' } });
|
||||
}
|
||||
|
||||
|
||||
@@ -59,22 +59,26 @@ const PrivacyScreen = () => {
|
||||
control={form.control}
|
||||
render={({ field: { onChange, value } }) => (
|
||||
<>
|
||||
<Pressable onPress={() => onChange('share-telemetry')}>
|
||||
<Pressable onPress={() => onChange('full')}>
|
||||
<RadioButton
|
||||
title="Share anonymous usage"
|
||||
description="Share completely anonymous telemetry data to help the developers improve the app"
|
||||
isSelected={value === 'share-telemetry'}
|
||||
title="Share anonymous usage data"
|
||||
description="This give us a completely anonymous picture of how you use Spacedrive."
|
||||
isSelected={value === 'full'}
|
||||
style={tw`mb-3 mt-4`}
|
||||
/>
|
||||
</Pressable>
|
||||
<Pressable
|
||||
testID="share-minimal"
|
||||
onPress={() => onChange('minimal-telemetry')}
|
||||
>
|
||||
<Pressable testID="share-minimal" onPress={() => onChange('minimal')}>
|
||||
<RadioButton
|
||||
title="Share the bare minimum"
|
||||
description="Only share that I am an active user of Spacedrive and a few technical bits"
|
||||
isSelected={value === 'minimal-telemetry'}
|
||||
title="Share minimal data"
|
||||
description="This just tells us how many people use Spacedrive and device/version details."
|
||||
isSelected={value === 'minimal'}
|
||||
/>
|
||||
</Pressable>
|
||||
<Pressable testID="share-none" onPress={() => onChange('none')}>
|
||||
<RadioButton
|
||||
title="Don't share anything"
|
||||
description="Sends absolutely no analytics data from the Spacedrive app."
|
||||
isSelected={value === 'none'}
|
||||
/>
|
||||
</Pressable>
|
||||
</>
|
||||
|
||||
@@ -131,12 +131,18 @@ describe('Onboarding', () => {
|
||||
cy.get('h2').should('contain', 'Your Privacy');
|
||||
|
||||
// Check we have all privacy options
|
||||
cy.get('label').contains("Don't share anything").click();
|
||||
cy.get('#radiofull').should('have.attr', 'data-state', 'unchecked');
|
||||
cy.get('#radiominimal').should('have.attr', 'data-state', 'unchecked');
|
||||
cy.get('#radionone').should('have.attr', 'data-state', 'checked');
|
||||
cy.get('label').contains('Share the bare minimum').click();
|
||||
cy.get('#radiominimal-telemetry').should('have.attr', 'data-state', 'checked');
|
||||
cy.get('#radioshare-telemetry').should('have.attr', 'data-state', 'unchecked');
|
||||
cy.get('#radiofull').should('have.attr', 'data-state', 'unchecked');
|
||||
cy.get('#radiominimal').should('have.attr', 'data-state', 'checked');
|
||||
cy.get('#radionone').should('have.attr', 'data-state', 'unchecked');
|
||||
cy.get('label').contains('Share anonymous usage').click();
|
||||
cy.get('#radioshare-telemetry').should('have.attr', 'data-state', 'checked');
|
||||
cy.get('#radiominimal-telemetry').should('have.attr', 'data-state', 'unchecked');
|
||||
cy.get('#radiofull').should('have.attr', 'data-state', 'checked');
|
||||
cy.get('#radiominimal').should('have.attr', 'data-state', 'unchecked');
|
||||
cy.get('#radionone').should('have.attr', 'data-state', 'unchecked');
|
||||
|
||||
// Check More info button exists and point to the valid pravacy policy
|
||||
cy.get('button').contains('More info').click();
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
FallbackProps
|
||||
} from 'react-error-boundary';
|
||||
import { useRouteError } from 'react-router';
|
||||
import { useDebugState } from '@sd/client';
|
||||
import { useDebugState, useTelemetryState } from '@sd/client';
|
||||
import { Button, Dialogs } from '@sd/ui';
|
||||
|
||||
import { showAlertDialog } from './components';
|
||||
@@ -68,6 +68,7 @@ export function ErrorPage({
|
||||
}) {
|
||||
useTheme();
|
||||
const debug = useDebugState();
|
||||
const { telemetryLevelPreference } = useTelemetryState();
|
||||
const os = useOperatingSystem();
|
||||
const platform = usePlatform();
|
||||
const isMacOS = os === 'macOS';
|
||||
@@ -134,19 +135,21 @@ export function ErrorPage({
|
||||
{t('reload')}
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
variant="gray"
|
||||
className="mt-2"
|
||||
onClick={() =>
|
||||
sendReportBtn
|
||||
? sendReportBtn()
|
||||
: sentryBrowserLazy.then(({ captureException }) =>
|
||||
captureException(message)
|
||||
)
|
||||
}
|
||||
>
|
||||
{t('send_report')}
|
||||
</Button>
|
||||
{telemetryLevelPreference !== 'none' && (
|
||||
<Button
|
||||
variant="gray"
|
||||
className="mt-2"
|
||||
onClick={() =>
|
||||
sendReportBtn
|
||||
? sendReportBtn()
|
||||
: sentryBrowserLazy.then(({ captureException }) =>
|
||||
captureException(message)
|
||||
)
|
||||
}
|
||||
>
|
||||
{t('send_report')}
|
||||
</Button>
|
||||
)}
|
||||
{platform.openLogsDir && (
|
||||
<Button variant="gray" className="mt-2" onClick={platform.openLogsDir}>
|
||||
{t('open_logs')}
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
useRspcLibraryContext,
|
||||
useSelector
|
||||
} from '@sd/client';
|
||||
import { useShortcut } from '~/hooks';
|
||||
import { useOperatingSystem, useShortcut } from '~/hooks';
|
||||
|
||||
import { useTopBarContext } from '../TopBar/Context';
|
||||
import { useExplorerContext } from './Context';
|
||||
@@ -45,6 +45,7 @@ export default function Explorer(props: PropsWithChildren<Props>) {
|
||||
s.showInspector,
|
||||
s.isTagAssignModeActive
|
||||
]);
|
||||
const isWindows = useOperatingSystem() === 'windows';
|
||||
|
||||
const showPathBar = explorer.showPathBar && layoutStore.showPathBar;
|
||||
const rspc = useRspcLibraryContext();
|
||||
@@ -95,6 +96,8 @@ export default function Explorer(props: PropsWithChildren<Props>) {
|
||||
className="explorer-scroll explorer-inspector-scroll flex flex-1 flex-col overflow-x-hidden"
|
||||
style={
|
||||
{
|
||||
'--scrollbar-width': isWindows ? '10px' : '6px',
|
||||
'--scrollbar-height': isWindows ? '10px' : '6px',
|
||||
'--scrollbar-margin-top': `${topBar.topBarHeight}px`,
|
||||
'--scrollbar-margin-bottom': `${showPathBar ? PATH_BAR_HEIGHT + (showTagBar ? TAG_BAR_HEIGHT : 0) : 0}px`,
|
||||
'paddingTop': topBar.topBarHeight,
|
||||
@@ -133,7 +136,10 @@ export default function Explorer(props: PropsWithChildren<Props>) {
|
||||
|
||||
{showInspector && (
|
||||
<Inspector
|
||||
className={clsx('no-scrollbar absolute right-1.5 top-0 pb-3 pl-3 pr-1.5')}
|
||||
className={clsx(
|
||||
'no-scrollbar absolute top-0 pb-3 pl-3 pr-1.5',
|
||||
isWindows ? 'right-3' : 'right-1.5'
|
||||
)}
|
||||
style={{
|
||||
paddingTop: topBar.topBarHeight + 12,
|
||||
bottom: showPathBar
|
||||
|
||||
@@ -3,7 +3,7 @@ import { Suspense, useEffect, useMemo, useRef } from 'react';
|
||||
import { Navigate, Outlet, useNavigate } from 'react-router-dom';
|
||||
import {
|
||||
ClientContextProvider,
|
||||
initPlausible,
|
||||
configureAnalyticsProperties,
|
||||
LibraryContextProvider,
|
||||
useBridgeQuery,
|
||||
useClientContext,
|
||||
@@ -144,16 +144,20 @@ function usePlausible() {
|
||||
const plausibleEvent = usePlausibleEvent();
|
||||
|
||||
useEffect(() => {
|
||||
initPlausible({
|
||||
configureAnalyticsProperties({
|
||||
buildInfo,
|
||||
platformType: platform === 'tauri' ? 'desktop' : 'web'
|
||||
});
|
||||
}, [platform, buildInfo]);
|
||||
|
||||
useEffect(() => {
|
||||
const interval = setInterval(() => {
|
||||
plausibleEvent({ event: { type: 'ping' } });
|
||||
}, 600 * 1000); // 10 minutes
|
||||
const interval = setInterval(
|
||||
() => {
|
||||
// ping every 10 minutes -- this just tells us that Spacedrive is running and helps us gauge the amount of active users we have.
|
||||
plausibleEvent({ event: { type: 'ping' } });
|
||||
},
|
||||
10 * 60 * 1_000
|
||||
);
|
||||
|
||||
return () => clearInterval(interval);
|
||||
}, [plausibleEvent]);
|
||||
|
||||
@@ -82,7 +82,7 @@ export const SearchOptionSubMenu = (
|
||||
<SearchOptionItemInternals {...props}>{props.name}</SearchOptionItemInternals>
|
||||
</ContextMenuDivItem>
|
||||
}
|
||||
className={clsx(MENU_STYLES, 'explorer-scroll -mt-1.5 max-h-80', props.className)}
|
||||
className={clsx(MENU_STYLES, 'default-scroll -mt-1.5 max-h-80', props.className)}
|
||||
>
|
||||
{props.children}
|
||||
</DropdownMenu.SubMenu>
|
||||
@@ -237,7 +237,7 @@ function AddFilterButton() {
|
||||
onKeyDown={(e) => e.stopPropagation()}
|
||||
className={clsx(
|
||||
MENU_STYLES,
|
||||
'explorer-scroll max-h-[80vh] min-h-[100px] min-w-[200px] max-w-fit'
|
||||
'default-scroll max-h-[80vh] min-h-[100px] min-w-[200px] max-w-fit'
|
||||
)}
|
||||
trigger={
|
||||
<Button className="flex flex-row gap-1" size="xs" variant="dotted">
|
||||
|
||||
@@ -15,7 +15,7 @@ interface Props {
|
||||
infoUrl?: string;
|
||||
}
|
||||
|
||||
export default ({ mini, registerName, ...props }: PropsWithChildren<Props>) => {
|
||||
export default function Setting({ mini, registerName, ...props }: PropsWithChildren<Props>) {
|
||||
const platform = usePlatform();
|
||||
|
||||
if (typeof props.description === 'string')
|
||||
@@ -38,7 +38,7 @@ export default ({ mini, registerName, ...props }: PropsWithChildren<Props>) => {
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
<div className="w-[85%]">{props.description}</div>
|
||||
<div className="text-balance">{props.description}</div>
|
||||
{!mini && props.children}
|
||||
</div>
|
||||
{mini && props.children}
|
||||
@@ -48,4 +48,4 @@ export default ({ mini, registerName, ...props }: PropsWithChildren<Props>) => {
|
||||
) : null}
|
||||
</>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,18 +1,26 @@
|
||||
import { telemetryState, useTelemetryState } from '@sd/client';
|
||||
import { Switch } from '@sd/ui';
|
||||
import { TelemetryLevelPreference, telemetryState, useTelemetryState } from '@sd/client';
|
||||
import { Select, SelectOption } from '@sd/ui';
|
||||
import i18n from '~/app/I18n';
|
||||
import { useLocale } from '~/hooks';
|
||||
|
||||
import { Heading } from '../Layout';
|
||||
import Setting from '../Setting';
|
||||
|
||||
export const Component = () => {
|
||||
const fullTelemetry = useTelemetryState().shareFullTelemetry;
|
||||
const telemetryPreferenceOptions = [
|
||||
{ value: 'full', label: i18n.t('telemetry_share_anonymous_short') },
|
||||
{ value: 'minimal', label: i18n.t('telemetry_share_minimal_short') },
|
||||
{ value: 'none', label: i18n.t('telemetry_share_none_short') }
|
||||
] satisfies { value: TelemetryLevelPreference; label: string }[];
|
||||
|
||||
export const Component = () => {
|
||||
const { t } = useLocale();
|
||||
|
||||
const { telemetryLevelPreference } = useTelemetryState();
|
||||
|
||||
return (
|
||||
<>
|
||||
<Heading title={t('privacy')} description="" />
|
||||
|
||||
<Setting
|
||||
mini
|
||||
toolTipLabel={t('learn_more_about_telemetry')}
|
||||
@@ -20,11 +28,20 @@ export const Component = () => {
|
||||
title={t('telemetry_title')}
|
||||
description={t('telemetry_description')}
|
||||
>
|
||||
<Switch
|
||||
checked={fullTelemetry}
|
||||
onClick={() => (telemetryState.shareFullTelemetry = !fullTelemetry)}
|
||||
size="md"
|
||||
/>
|
||||
<Select
|
||||
value={telemetryLevelPreference}
|
||||
onChange={(newValue) => {
|
||||
// add "dateFormat" key to localStorage and set it as default date format
|
||||
telemetryState.telemetryLevelPreference = newValue;
|
||||
}}
|
||||
containerClassName="flex h-[30px] gap-2"
|
||||
>
|
||||
{telemetryPreferenceOptions.map((format, index) => (
|
||||
<SelectOption key={index} value={format.value}>
|
||||
{format.label}
|
||||
</SelectOption>
|
||||
))}
|
||||
</Select>
|
||||
</Setting>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -38,16 +38,21 @@ export const useContextValue = () => {
|
||||
};
|
||||
|
||||
export const shareTelemetry = RadioGroupField.options([
|
||||
z.literal('share-telemetry'),
|
||||
z.literal('minimal-telemetry')
|
||||
z.literal('full'),
|
||||
z.literal('minimal'),
|
||||
z.literal('none')
|
||||
]).details({
|
||||
'share-telemetry': {
|
||||
heading: i18n.t('share_anonymous_usage'),
|
||||
description: i18n.t('share_anonymous_usage_description')
|
||||
full: {
|
||||
heading: i18n.t('telemetry_share_anonymous'),
|
||||
description: i18n.t('telemetry_share_anonymous_description')
|
||||
},
|
||||
'minimal-telemetry': {
|
||||
heading: i18n.t('share_bare_minimum'),
|
||||
description: i18n.t('share_bare_minimum_description')
|
||||
minimal: {
|
||||
heading: i18n.t('telemetry_share_minimal'),
|
||||
description: i18n.t('telemetry_share_minimal_description')
|
||||
},
|
||||
none: {
|
||||
heading: i18n.t('telemetry_share_none'),
|
||||
description: i18n.t('telemetry_share_none_description')
|
||||
}
|
||||
});
|
||||
|
||||
@@ -104,7 +109,7 @@ const useFormState = () => {
|
||||
|
||||
// opted to place this here as users could change their mind before library creation/onboarding finalization
|
||||
// it feels more fitting to configure it here (once)
|
||||
telemetryState.shareFullTelemetry = data.privacy.shareTelemetry === 'share-telemetry';
|
||||
telemetryState.telemetryLevelPreference = data.privacy.shareTelemetry;
|
||||
|
||||
try {
|
||||
// show creation screen for a bit for smoothness
|
||||
@@ -119,7 +124,7 @@ const useFormState = () => {
|
||||
|
||||
if (platform.refreshMenuBar) platform.refreshMenuBar();
|
||||
|
||||
if (telemetryState.shareFullTelemetry) {
|
||||
if (telemetryState.telemetryLevelPreference === 'full') {
|
||||
submitPlausibleEvent({ event: { type: 'libraryCreate' } });
|
||||
}
|
||||
|
||||
|
||||
@@ -113,8 +113,8 @@ body {
|
||||
|
||||
.explorer-scroll {
|
||||
&::-webkit-scrollbar {
|
||||
height: 6px;
|
||||
width: 6px;
|
||||
height: var(--scrollbar-height);
|
||||
width: var(--scrollbar-width);
|
||||
}
|
||||
&::-webkit-scrollbar-track {
|
||||
@apply rounded-[6px] bg-transparent;
|
||||
@@ -129,13 +129,13 @@ body {
|
||||
.default-scroll {
|
||||
&::-webkit-scrollbar {
|
||||
height: 6px;
|
||||
width: 8px;
|
||||
width: 6px;
|
||||
}
|
||||
&::-webkit-scrollbar-track {
|
||||
@apply rounded-[6px] bg-transparent;
|
||||
}
|
||||
&::-webkit-scrollbar-thumb {
|
||||
@apply rounded-[6px] bg-app-box;
|
||||
@apply rounded-[6px] bg-app-explorerScrollbar;
|
||||
}
|
||||
}
|
||||
.page-scroll {
|
||||
|
||||
@@ -629,10 +629,6 @@
|
||||
"settings": "الإعدادات",
|
||||
"setup": "إعداد",
|
||||
"share": "مشاركة",
|
||||
"share_anonymous_usage": "مشاركة الاستخدام المجهول",
|
||||
"share_anonymous_usage_description": "مشاركة بيانات التلميح المجهولة تمامًا لمساعدة المطورين في تحسين التطبيق",
|
||||
"share_bare_minimum": "مشاركة الحد الأدنى",
|
||||
"share_bare_minimum_description": "مشاركة أنني مستخدم نشط لـ Spacedrive وبعض البيانات الفنية فقط",
|
||||
"sharing": "المشاركة",
|
||||
"sharing_description": "إدارة من لديه وصول إلى مكتباتك.",
|
||||
"show_details": "إظهار التفاصيل",
|
||||
@@ -705,6 +701,15 @@
|
||||
"task_two": "tasks",
|
||||
"task_zero": "task",
|
||||
"telemetry_description": "قم بتبديل التشغيل لتزويد المطورين ببيانات مفصلة حول الاستخدام وبيانات التلميح لتحسين التطبيق. قم بتبديل التشغيل لإرسال بيانات أساسية فقط: حالة نشاطك ، إصدار التطبيق ، إصدار النواة ، والمنصة (مثل الجوال أو الويب أو سطح المكتب).",
|
||||
"telemetry_share_anonymous": "مشاركة الاستخدام المجهول",
|
||||
"telemetry_share_anonymous_description": "مشاركة بيانات التلميح المجهولة تمامًا لمساعدة المطورين في تحسين التطبيق",
|
||||
"telemetry_share_anonymous_short": "استخدام مجهول",
|
||||
"telemetry_share_minimal": "مشاركة الحد الأدنى",
|
||||
"telemetry_share_minimal_description": "مشاركة أنني مستخدم نشط لـ Spacedrive وبعض البيانات الفنية فقط",
|
||||
"telemetry_share_minimal_short": "الحد الأدنى",
|
||||
"telemetry_share_none": "لا تشارك",
|
||||
"telemetry_share_none_description": "لا يرسل أي بيانات تحليلية على الإطلاق من تطبيق Spacedrive.",
|
||||
"telemetry_share_none_short": "لا أحد",
|
||||
"telemetry_title": "مشاركة بيانات التلميح والاستخدام الإضافية",
|
||||
"temperature": "درجة الحرارة",
|
||||
"text": "Text",
|
||||
|
||||
@@ -685,10 +685,6 @@
|
||||
"settings": "Налады",
|
||||
"setup": "Наладзіць",
|
||||
"share": "Падзяліцца",
|
||||
"share_anonymous_usage": "Падзяляцца ананімнымі дадзенымі пра выкарыстанне",
|
||||
"share_anonymous_usage_description": "Падзяляйцеся цалкам ананімнымі тэлеметрычнымі дадзенымі, каб дапамагчы распрацоўнікам палепшыць дадатак",
|
||||
"share_bare_minimum": "Падзяляцца толькі патрэбным",
|
||||
"share_bare_minimum_description": "Падзяляцца толькі тым, што з'яўляецеся актыўным карыстачом Spacedrive і некалькімі тэхнічнымі момантамі.",
|
||||
"sharing": "Супольнае выкарыстанне",
|
||||
"sharing_description": "Кіруйце тым, хто мае доступ да вашых бібліятэк.",
|
||||
"show_details": "Паказаць падрабязнасці",
|
||||
@@ -762,6 +758,15 @@
|
||||
"task_many": "задач",
|
||||
"task_one": "задача",
|
||||
"telemetry_description": "Уключыце, каб падаць распрацоўнікам дэталёвыя дадзеныя пра выкарыстанне і тэлеметрыю для паляпшэння дадатку. Выключыце, каб адпраўляць толькі асноўныя дадзеныя: статус актыўнасці, версію дадатку, версію ядра і платформу (прыкладам, мабільную, ўэб- ці настольную).",
|
||||
"telemetry_share_anonymous": "Падзяляцца ананімнымі дадзенымі пра выкарыстанне",
|
||||
"telemetry_share_anonymous_description": "Падзяляйцеся цалкам ананімнымі тэлеметрычнымі дадзенымі, каб дапамагчы распрацоўнікам палепшыць дадатак",
|
||||
"telemetry_share_anonymous_short": "Ананімнае выкарыстанне",
|
||||
"telemetry_share_minimal": "Падзяляцца толькі патрэбным",
|
||||
"telemetry_share_minimal_description": "Падзяляцца толькі тым, што з'яўляецеся актыўным карыстачом Spacedrive і некалькімі тэхнічнымі момантамі.",
|
||||
"telemetry_share_minimal_short": "Мінімальны",
|
||||
"telemetry_share_none": "Не дзяліцеся",
|
||||
"telemetry_share_none_description": "Не адпраўляе абсалютна ніякіх аналітычных даных з праграмы Spacedrive.",
|
||||
"telemetry_share_none_short": "Няма",
|
||||
"telemetry_title": "Падаванне дадатковай тэлеметрыi і дадзеных пра выкарыстанне",
|
||||
"temperature": "Тэмпература",
|
||||
"text": "Тэкст",
|
||||
|
||||
@@ -658,10 +658,6 @@
|
||||
"settings": "Nastavení",
|
||||
"setup": "Nastavit",
|
||||
"share": "Sdílet",
|
||||
"share_anonymous_usage": "Sdílet anonymní používání",
|
||||
"share_anonymous_usage_description": "Sdílejte zcela anonymní telemetrická data, která pomáhají vývojářům zlepšovat aplikaci",
|
||||
"share_bare_minimum": "Sdílet pouze minimum",
|
||||
"share_bare_minimum_description": "Sdílet pouze to, že jsem aktivním uživatelem Spacedrive a několik technických detailů",
|
||||
"sharing": "Sdílení",
|
||||
"sharing_description": "Spravujte, kdo má přístup k vašim knihovnám.",
|
||||
"show_details": "Zobrazit podrobnosti",
|
||||
@@ -733,6 +729,15 @@
|
||||
"task_one": "úkol",
|
||||
"task_other": "úkoly",
|
||||
"telemetry_description": "Přepněte na ON, aby vývojáři mohli získat podrobné údaje o používání a telemetrii pro vylepšení aplikace. Přepněte na OFF, abyste poslali pouze základní údaje: váš stav aktivity, verzi aplikace, verzi jádra a platformu (např. mobilní, webová nebo desktopová).",
|
||||
"telemetry_share_anonymous": "Sdílet anonymní používání",
|
||||
"telemetry_share_anonymous_description": "Sdílejte zcela anonymní telemetrická data, která pomáhají vývojářům zlepšovat aplikaci",
|
||||
"telemetry_share_anonymous_short": "Anonymní použití",
|
||||
"telemetry_share_minimal": "Sdílet pouze minimum",
|
||||
"telemetry_share_minimal_description": "Sdílet pouze to, že jsem aktivním uživatelem Spacedrive a několik technických detailů",
|
||||
"telemetry_share_minimal_short": "Minimální",
|
||||
"telemetry_share_none": "Nesdílejte",
|
||||
"telemetry_share_none_description": "Neposílá absolutně žádná analytická data z aplikace Spacedrive.",
|
||||
"telemetry_share_none_short": "Žádný",
|
||||
"telemetry_title": "Sdílet další telemetrické a uživatelské údaje",
|
||||
"temperature": "Teplota",
|
||||
"text": "Text",
|
||||
|
||||
@@ -611,10 +611,6 @@
|
||||
"settings": "Einstellungen",
|
||||
"setup": "Einrichten",
|
||||
"share": "Teilen",
|
||||
"share_anonymous_usage": "Anonyme Nutzung teilen",
|
||||
"share_anonymous_usage_description": "Teile völlig anonyme Telemetriedaten, um den Entwicklern bei der Verbesserung der App zu helfen",
|
||||
"share_bare_minimum": "Mindestinformationen teilen",
|
||||
"share_bare_minimum_description": "Nur teilen, dass ich ein aktiver Benutzer von Spacedrive bin und einige technische Details",
|
||||
"sharing": "Teilen",
|
||||
"sharing_description": "Verwalte, wer Zugriff auf Deine Bibliotheken hat.",
|
||||
"show_details": "Details anzeigen",
|
||||
@@ -679,6 +675,15 @@
|
||||
"task_one": "task",
|
||||
"task_other": "tasks",
|
||||
"telemetry_description": "Schalte EIN, um den Entwicklern detaillierte Nutzung- und Telemetriedaten zur Verfügung zu stellen, die die App verbessern helfen. Schalten AUS, um nur grundlegende Daten zu senden: Deinen Aktivitätsstatus, die App-Version, die Core-Version und die Plattform (z.B. Mobil, Web oder Desktop).",
|
||||
"telemetry_share_anonymous": "Anonyme Nutzung teilen",
|
||||
"telemetry_share_anonymous_description": "Teile völlig anonyme Telemetriedaten, um den Entwicklern bei der Verbesserung der App zu helfen",
|
||||
"telemetry_share_anonymous_short": "Anonyme Nutzung",
|
||||
"telemetry_share_minimal": "Mindestinformationen teilen",
|
||||
"telemetry_share_minimal_description": "Nur teilen, dass ich ein aktiver Benutzer von Spacedrive bin und einige technische Details",
|
||||
"telemetry_share_minimal_short": "Minimal",
|
||||
"telemetry_share_none": "Nicht teilen",
|
||||
"telemetry_share_none_description": "Sendet absolut keine Analysedaten von der Spacedrive-App.",
|
||||
"telemetry_share_none_short": "Keiner",
|
||||
"telemetry_title": "Zusätzliche Telemetrie- und Nutzungsdaten teilen",
|
||||
"temperature": "Temperatur",
|
||||
"text": "Text",
|
||||
|
||||
@@ -659,10 +659,6 @@
|
||||
"settings": "Settings",
|
||||
"setup": "Set up",
|
||||
"share": "Share",
|
||||
"share_anonymous_usage": "Share anonymous usage",
|
||||
"share_anonymous_usage_description": "Share completely anonymous telemetry data to help the developers improve the app",
|
||||
"share_bare_minimum": "Share the bare minimum",
|
||||
"share_bare_minimum_description": "Only share that I am an active user of Spacedrive and a few technical bits",
|
||||
"sharing": "Sharing",
|
||||
"sharing_description": "Manage who has access to your libraries.",
|
||||
"show_details": "Show details",
|
||||
@@ -733,8 +729,17 @@
|
||||
"task": "task",
|
||||
"task_one": "task",
|
||||
"task_other": "tasks",
|
||||
"telemetry_description": "Toggle ON to provide developers with detailed usage and telemetry data to enhance the app. Toggle OFF to send only basic data: your activity status, app version, core version, and platform (e.g., mobile, web, or desktop).",
|
||||
"telemetry_title": "Share Additional Telemetry and Usage Data",
|
||||
"telemetry_description": "You can choose to provide Spacedrive's developers with anonymous telemetry data to enhance the app. “None” sends no analytics data whatsoever. “Minimal” sends basic usage & version stats. “Anonymous usage” sends strictly anonymized usage details that cannot be linked to you.",
|
||||
"telemetry_share_anonymous": "Share anonymous usage",
|
||||
"telemetry_share_anonymous_description": "Shares completely anonymous telemetry data to help our team improve the app.",
|
||||
"telemetry_share_anonymous_short": "Anonymous usage",
|
||||
"telemetry_share_minimal": "Share the bare minimum",
|
||||
"telemetry_share_minimal_description": "Only shares anonymously that Spacedrive is active and a few technical bits.",
|
||||
"telemetry_share_minimal_short": "Minimal",
|
||||
"telemetry_share_none": "Don't share anything",
|
||||
"telemetry_share_none_description": "Sends absolutely no analytics data from the Spacedrive app.",
|
||||
"telemetry_share_none_short": "None",
|
||||
"telemetry_title": "Telemetry & usage data sharing",
|
||||
"temperature": "Temperature",
|
||||
"text": "Text",
|
||||
"text_file": "Text File",
|
||||
|
||||
@@ -613,10 +613,6 @@
|
||||
"settings": "Configuraciones",
|
||||
"setup": "Configurar",
|
||||
"share": "Compartir",
|
||||
"share_anonymous_usage": "Compartir uso anónimo",
|
||||
"share_anonymous_usage_description": "Compartir datos de telemetría completamente anónimos para ayudar a los desarrolladores a mejorar la aplicación",
|
||||
"share_bare_minimum": "Compartir lo mínimo indispensable",
|
||||
"share_bare_minimum_description": "Solo compartir que soy un usuario activo de Spacedrive y algunos detalles técnicos",
|
||||
"sharing": "Compartiendo",
|
||||
"sharing_description": "Administra quién tiene acceso a tus bibliotecas.",
|
||||
"show_details": "Mostrar detalles",
|
||||
@@ -681,6 +677,15 @@
|
||||
"task_one": "task",
|
||||
"task_other": "tasks",
|
||||
"telemetry_description": "Activa para proporcionar a los desarrolladores datos detallados de uso y telemetría para mejorar la aplicación. Desactiva para enviar solo datos básicos: tu estado de actividad, versión de la aplicación, versión central y plataforma (por ejemplo, móvil, web o escritorio).",
|
||||
"telemetry_share_anonymous": "Compartir uso anónimo",
|
||||
"telemetry_share_anonymous_description": "Compartir datos de telemetría completamente anónimos para ayudar a los desarrolladores a mejorar la aplicación",
|
||||
"telemetry_share_anonymous_short": "Uso anónimo",
|
||||
"telemetry_share_minimal": "Compartir lo mínimo indispensable",
|
||||
"telemetry_share_minimal_description": "Solo compartir que soy un usuario activo de Spacedrive y algunos detalles técnicos",
|
||||
"telemetry_share_minimal_short": "Mínimo",
|
||||
"telemetry_share_none": "No compartir",
|
||||
"telemetry_share_none_description": "No envía absolutamente ningún dato analítico desde la aplicación Spacedrive.",
|
||||
"telemetry_share_none_short": "Ninguno",
|
||||
"telemetry_title": "Compartir datos adicionales de telemetría y uso",
|
||||
"temperature": "Temperatura",
|
||||
"text": "Text",
|
||||
|
||||
@@ -613,10 +613,6 @@
|
||||
"settings": "Paramètres",
|
||||
"setup": "Configuration",
|
||||
"share": "Partager",
|
||||
"share_anonymous_usage": "Partager l'utilisation anonyme",
|
||||
"share_anonymous_usage_description": "Partager des données de télémétrie complètement anonymes pour aider les développeurs à améliorer l'application",
|
||||
"share_bare_minimum": "Partager le strict minimum",
|
||||
"share_bare_minimum_description": "Partager uniquement que je suis un utilisateur actif de Spacedrive et quelques détails techniques",
|
||||
"sharing": "Partage",
|
||||
"sharing_description": "Gérer qui a accès à vos bibliothèques.",
|
||||
"show_details": "Afficher les détails",
|
||||
@@ -680,6 +676,15 @@
|
||||
"task_one": "task",
|
||||
"task_other": "tasks",
|
||||
"telemetry_description": "Activez pour fournir aux développeurs des données détaillées d'utilisation et de télémesure afin d'améliorer l'application. Désactivez pour n'envoyer que les données de base : votre statut d'activité, la version de l'application, la version du noyau et la plateforme (par exemple, mobile, web ou ordinateur de bureau).",
|
||||
"telemetry_share_anonymous": "Partager l'utilisation anonyme",
|
||||
"telemetry_share_anonymous_description": "Partager des données de télémétrie complètement anonymes pour aider les développeurs à améliorer l'application",
|
||||
"telemetry_share_anonymous_short": "Utilisation anonyme",
|
||||
"telemetry_share_minimal": "Partager le strict minimum",
|
||||
"telemetry_share_minimal_description": "Partager uniquement que je suis un utilisateur actif de Spacedrive et quelques détails techniques",
|
||||
"telemetry_share_minimal_short": "Minimal",
|
||||
"telemetry_share_none": "Ne partagez pas",
|
||||
"telemetry_share_none_description": "N'envoie absolument aucune donnée analytique depuis l'application Spacedrive.",
|
||||
"telemetry_share_none_short": "Aucun",
|
||||
"telemetry_title": "Partager des données de télémesure et d'utilisation supplémentaires",
|
||||
"temperature": "Température",
|
||||
"text": "Text",
|
||||
|
||||
@@ -613,10 +613,6 @@
|
||||
"settings": "Impostazioni",
|
||||
"setup": "Imposta",
|
||||
"share": "Condividi",
|
||||
"share_anonymous_usage": "Condividi l'utilizzo in modo anonimo",
|
||||
"share_anonymous_usage_description": "Condividi dati di telemetria in maniera completamente anonima per aiutare gli sviluppatori a migliorare l'app",
|
||||
"share_bare_minimum": "Condividi il minimo necessario",
|
||||
"share_bare_minimum_description": "Condividi solo che sono un utente attivo di Spacedrive e altri dettagli tecnici",
|
||||
"sharing": "Condivisione",
|
||||
"sharing_description": "Gestisci chi ha accesso alle tue librerie.",
|
||||
"show_details": "Mostra dettagli",
|
||||
@@ -680,6 +676,15 @@
|
||||
"task_one": "task",
|
||||
"task_other": "tasks",
|
||||
"telemetry_description": "Attiva per fornire agli sviluppatori dati dettagliati sull'utilizzo e sulla telemetria per migliorare l'app. Disattiva per inviare solo i dati di base: stato della tua attività, versione dell'app, versione principale e piattaforma (ad esempio mobile, web o desktop).",
|
||||
"telemetry_share_anonymous": "Condividi l'utilizzo in modo anonimo",
|
||||
"telemetry_share_anonymous_description": "Condividi dati di telemetria in maniera completamente anonima per aiutare gli sviluppatori a migliorare l'app",
|
||||
"telemetry_share_anonymous_short": "Utilizzo anonimo",
|
||||
"telemetry_share_minimal": "Condividi il minimo necessario",
|
||||
"telemetry_share_minimal_description": "Condividi solo che sono un utente attivo di Spacedrive e altri dettagli tecnici",
|
||||
"telemetry_share_minimal_short": "Minimo",
|
||||
"telemetry_share_none": "Non condividere",
|
||||
"telemetry_share_none_description": "Non invia assolutamente dati analitici dall'app Spacedrive.",
|
||||
"telemetry_share_none_short": "Nessuno",
|
||||
"telemetry_title": "Condividi ulteriori dati di telemetria e utilizzo",
|
||||
"temperature": "Temperatura",
|
||||
"text": "Text",
|
||||
|
||||
@@ -604,10 +604,6 @@
|
||||
"settings": "設定",
|
||||
"setup": "セットアップ",
|
||||
"share": "共有",
|
||||
"share_anonymous_usage": "利用状況を送信する",
|
||||
"share_anonymous_usage_description": "アプリの改善のために、完全に匿名のテレメトリデータを送信します",
|
||||
"share_bare_minimum": "最小限のデータのみを送信する",
|
||||
"share_bare_minimum_description": "自分がSpacedriveのアクティブユーザーであることと、多少の技術的データのみを送信します",
|
||||
"sharing": "シェアリング",
|
||||
"sharing_description": "ライブラリへのアクセス権を管理できます。",
|
||||
"show_details": "詳細を表示",
|
||||
@@ -670,6 +666,15 @@
|
||||
"task": "task",
|
||||
"task_other": "tasks",
|
||||
"telemetry_description": "有効にすると、アプリを改善するための詳細なテレメトリ・利用状況データが開発者に提供されます。無効にすると、基本的なデータ(実行状況、アプリバージョン、コアバージョン、プラットフォーム[モバイル/ウェブ/デスクトップなど])のみが送信されます。",
|
||||
"telemetry_share_anonymous": "利用状況を送信する",
|
||||
"telemetry_share_anonymous_description": "アプリの改善のために、完全に匿名のテレメトリデータを送信します",
|
||||
"telemetry_share_anonymous_short": "匿名での使用",
|
||||
"telemetry_share_minimal": "最小限のデータのみを送信する",
|
||||
"telemetry_share_minimal_description": "自分がSpacedriveのアクティブユーザーであることと、多少の技術的データのみを送信します",
|
||||
"telemetry_share_minimal_short": "最小限",
|
||||
"telemetry_share_none": "共有しないでください",
|
||||
"telemetry_share_none_description": "Spacedrive アプリから分析データをまったく送信しません。",
|
||||
"telemetry_share_none_short": "なし",
|
||||
"telemetry_title": "テレメトリ・利用状況データを送信する",
|
||||
"temperature": "温度",
|
||||
"text": "テキスト",
|
||||
|
||||
@@ -611,10 +611,6 @@
|
||||
"settings": "Instellingen",
|
||||
"setup": "Instellen",
|
||||
"share": "Delen",
|
||||
"share_anonymous_usage": "Deel anonieme gebruiksgegevens",
|
||||
"share_anonymous_usage_description": "Deel volledig anonieme telemetrie gegevens om de ontwikkelaars te helpen de app te verbeteren",
|
||||
"share_bare_minimum": "Deel het absolute minimale",
|
||||
"share_bare_minimum_description": "Deel alleen dat ik een actieve gebruiker ben van Spacedrive en een paar technische details",
|
||||
"sharing": "Delen",
|
||||
"sharing_description": "Beheer wie toegang heeft tot je bibliotheken.",
|
||||
"show_details": "Toon details",
|
||||
@@ -679,6 +675,15 @@
|
||||
"task_one": "task",
|
||||
"task_other": "tasks",
|
||||
"telemetry_description": "Schakel in om de ontwikkelaars te voorzien van gedetailleerde gebruik en telemetrie gegevens om de app te verbeteren. Schakel uit om alleen basisgegevens te verzenden: je status, app-versie, core versie en platform (bijvoorbeeld, mobiel, browser, of desktop).",
|
||||
"telemetry_share_anonymous": "Deel anonieme gebruiksgegevens",
|
||||
"telemetry_share_anonymous_description": "Deel volledig anonieme telemetrie gegevens om de ontwikkelaars te helpen de app te verbeteren",
|
||||
"telemetry_share_anonymous_short": "Anoniem gebruik",
|
||||
"telemetry_share_minimal": "Deel het absolute minimale",
|
||||
"telemetry_share_minimal_description": "Deel alleen dat ik een actieve gebruiker ben van Spacedrive en een paar technische details",
|
||||
"telemetry_share_minimal_short": "Minimaal",
|
||||
"telemetry_share_none": "Deel niet",
|
||||
"telemetry_share_none_description": "Verzendt absoluut geen analysegegevens vanuit de Spacedrive-app.",
|
||||
"telemetry_share_none_short": "Geen",
|
||||
"telemetry_title": "Deel Aanvullende Telemetrie en Gebruiksgegevens",
|
||||
"temperature": "Temperatuur",
|
||||
"text": "Text",
|
||||
|
||||
@@ -685,10 +685,6 @@
|
||||
"settings": "Настройки",
|
||||
"setup": "Настроить",
|
||||
"share": "Поделиться",
|
||||
"share_anonymous_usage": "Делиться анонимными данными об использовании",
|
||||
"share_anonymous_usage_description": "Делитесь полностью анонимными телеметрическими данными, чтобы помочь разработчикам улучшить приложение",
|
||||
"share_bare_minimum": "Делиться лишь необходимым",
|
||||
"share_bare_minimum_description": "Делиться лишь тем, что являетесь активным пользователем Spacedrive и несколькими техническими моментами.",
|
||||
"sharing": "Совместное использование",
|
||||
"sharing_description": "Управляйте тем, кто имеет доступ к вашим библиотекам.",
|
||||
"show_details": "Показать подробности",
|
||||
@@ -762,6 +758,15 @@
|
||||
"task_many": "задач",
|
||||
"task_one": "задача",
|
||||
"telemetry_description": "Включите, чтобы предоставить разработчикам подробные данные об использовании и телеметрии для улучшения приложения. Выключите, чтобы отправлять только основные данные: статус активности, версию приложения, версию ядра и платформу (например, мобильную, веб- или настольную).",
|
||||
"telemetry_share_anonymous": "Делиться анонимными данными об использовании",
|
||||
"telemetry_share_anonymous_description": "Делитесь полностью анонимными телеметрическими данными, чтобы помочь разработчикам улучшить приложение",
|
||||
"telemetry_share_anonymous_short": "Анонимное использование",
|
||||
"telemetry_share_minimal": "Делиться лишь необходимым",
|
||||
"telemetry_share_minimal_description": "Делиться лишь тем, что являетесь активным пользователем Spacedrive и несколькими техническими моментами.",
|
||||
"telemetry_share_minimal_short": "Минимальный",
|
||||
"telemetry_share_none": "Не делитесь",
|
||||
"telemetry_share_none_description": "Не отправляет абсолютно никаких аналитических данных из приложения Spacedrive.",
|
||||
"telemetry_share_none_short": "Никто",
|
||||
"telemetry_title": "Предоставление дополнительной телеметрии и данных об использовании",
|
||||
"temperature": "Температура",
|
||||
"text": "Текст",
|
||||
|
||||
@@ -611,10 +611,6 @@
|
||||
"settings": "Ayarlar",
|
||||
"setup": "Kurulum",
|
||||
"share": "Paylaş",
|
||||
"share_anonymous_usage": "Anonim kullanımı paylaş",
|
||||
"share_anonymous_usage_description": "Geliştiricilere uygulamayı iyileştirmelerine yardımcı olmak için tamamen anonim telemetri verilerini paylaşın",
|
||||
"share_bare_minimum": "Sadece en azını paylaş",
|
||||
"share_bare_minimum_description": "Yalnızca Spacedrive'ın aktif bir kullanıcısı olduğumu ve birkaç teknik ayrıntıyı paylaşın",
|
||||
"sharing": "Paylaşım",
|
||||
"sharing_description": "Kütüphanelerinize kimlerin erişim sağlayabileceğini yönetin.",
|
||||
"show_details": "Detayları Göster",
|
||||
@@ -679,6 +675,15 @@
|
||||
"task_one": "task",
|
||||
"task_other": "tasks",
|
||||
"telemetry_description": "Uygulamayı iyileştirmek için geliştiricilere detaylı kullanım ve telemetri verisi sağlamak için AÇIK konumuna getirin. Yalnızca temel verileri göndermek için KAPALI konumuna getirin: faaliyet durumunuz, uygulama sürümü, çekirdek sürümü ve platform (örn., mobil, web veya masaüstü).",
|
||||
"telemetry_share_anonymous": "Anonim kullanımı paylaş",
|
||||
"telemetry_share_anonymous_description": "Geliştiricilere uygulamayı iyileştirmelerine yardımcı olmak için tamamen anonim telemetri verilerini paylaşın",
|
||||
"telemetry_share_anonymous_short": "Anonim kullanım",
|
||||
"telemetry_share_minimal": "Sadece en azını paylaş",
|
||||
"telemetry_share_minimal_description": "Yalnızca Spacedrive'ın aktif bir kullanıcısı olduğumu ve birkaç teknik ayrıntıyı paylaşın",
|
||||
"telemetry_share_minimal_short": "Asgari",
|
||||
"telemetry_share_none": "Paylaşma",
|
||||
"telemetry_share_none_description": "Spacedrive uygulamasından kesinlikle hiçbir analiz verisi göndermez.",
|
||||
"telemetry_share_none_short": "Hiçbiri",
|
||||
"telemetry_title": "Ek Telemetri ve Kullanım Verisi Paylaş",
|
||||
"temperature": "Sıcaklık",
|
||||
"text": "Text",
|
||||
|
||||
@@ -604,10 +604,6 @@
|
||||
"settings": "设置",
|
||||
"setup": "设置",
|
||||
"share": "分享",
|
||||
"share_anonymous_usage": "分享匿名使用情况",
|
||||
"share_anonymous_usage_description": "分享完全匿名的遥测数据,帮助开发者改进应用程序",
|
||||
"share_bare_minimum": "分享最基本信息",
|
||||
"share_bare_minimum_description": "只分享我是 Spacedrive 的活跃用户和一些技术细节",
|
||||
"sharing": "共享",
|
||||
"sharing_description": "管理有权访问您的库的人。",
|
||||
"show_details": "显示详情",
|
||||
@@ -671,6 +667,15 @@
|
||||
"task": "任务",
|
||||
"task_other": "任务",
|
||||
"telemetry_description": "启用以向开发者提供详细的使用情况和遥测数据来改善应用程序。禁用则将只发送基本数据:您的活动状态、应用版本、应用内核版本以及平台(例如移动端、web 端或桌面端)。",
|
||||
"telemetry_share_anonymous": "分享匿名使用情况",
|
||||
"telemetry_share_anonymous_description": "分享完全匿名的遥测数据,帮助开发者改进应用程序",
|
||||
"telemetry_share_anonymous_short": "匿名使用",
|
||||
"telemetry_share_minimal": "分享最基本信息",
|
||||
"telemetry_share_minimal_description": "只分享我是 Spacedrive 的活跃用户和一些技术细节",
|
||||
"telemetry_share_minimal_short": "最小",
|
||||
"telemetry_share_none": "请勿分享",
|
||||
"telemetry_share_none_description": "绝对不从 Spacedrive 应用程序发送任何分析数据。",
|
||||
"telemetry_share_none_short": "没有任何",
|
||||
"telemetry_title": "共享额外的遥测和使用数据",
|
||||
"temperature": "温度",
|
||||
"text": "文本",
|
||||
|
||||
@@ -604,10 +604,6 @@
|
||||
"settings": "設置",
|
||||
"setup": "設定",
|
||||
"share": "分享",
|
||||
"share_anonymous_usage": "分享匿名使用情況",
|
||||
"share_anonymous_usage_description": "分享完全匿名的遙測數據,以幫助開發人員改進應用程序",
|
||||
"share_bare_minimum": "分享最低限度",
|
||||
"share_bare_minimum_description": "僅分享我是Spacedrive的活躍用戶和一些技術細節",
|
||||
"sharing": "共享",
|
||||
"sharing_description": "管理誰可以訪問您的圖書館。",
|
||||
"show_details": "顯示詳情",
|
||||
@@ -671,6 +667,15 @@
|
||||
"task_one": "task",
|
||||
"task_other": "tasks",
|
||||
"telemetry_description": "切換到ON,為開發者提供詳細的使用情況和遙測數據以增強應用程序。切換到OFF,只發送基本數據:您的活動狀態,應用版本,核心版本,以及平台(例如移動,網絡或桌面)。",
|
||||
"telemetry_share_anonymous": "分享匿名使用情況",
|
||||
"telemetry_share_anonymous_description": "分享完全匿名的遙測數據,以幫助開發人員改進應用程序",
|
||||
"telemetry_share_anonymous_short": "匿名使用",
|
||||
"telemetry_share_minimal": "分享最低限度",
|
||||
"telemetry_share_minimal_description": "僅分享我是Spacedrive的活躍用戶和一些技術細節",
|
||||
"telemetry_share_minimal_short": "最小",
|
||||
"telemetry_share_none": "請勿分享",
|
||||
"telemetry_share_none_description": "絕對不從 Spacedrive 應用程式發送任何分析資料。",
|
||||
"telemetry_share_none_short": "沒有任何",
|
||||
"telemetry_title": "分享額外的遙測和使用情況數據",
|
||||
"temperature": "溫度",
|
||||
"text": "Text",
|
||||
|
||||
@@ -5,17 +5,10 @@ import { BuildInfo } from '../core';
|
||||
import { useDebugState } from '../stores/debugState';
|
||||
import { PlausiblePlatformType, telemetryState, useTelemetryState } from '../stores/telemetryState';
|
||||
|
||||
/**
|
||||
* This should be in sync with the Core's version.
|
||||
*/
|
||||
|
||||
const DOMAIN = 'app.spacedrive.com';
|
||||
const MOBILE_DOMAIN = 'mobile.spacedrive.com';
|
||||
|
||||
const PlausibleProvider = Plausible({
|
||||
trackLocalhost: true,
|
||||
domain: DOMAIN
|
||||
});
|
||||
let plausibleInstance: ReturnType<typeof Plausible>;
|
||||
|
||||
/**
|
||||
* This defines all possible options that may be provided by events upon submission.
|
||||
@@ -23,13 +16,10 @@ const PlausibleProvider = Plausible({
|
||||
* This extends the standard options provided by the `plausible-tracker`
|
||||
* package, but also offers some additiional options for custom functionality.
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
||||
interface PlausibleOptions extends PlausibleTrackerOptions {
|
||||
/**
|
||||
* This should **only** be used in contexts where telemetry sharing
|
||||
* must be allowed/denied via external means. Currently it is not used by anything,
|
||||
* but probably will be in the future.
|
||||
*/
|
||||
telemetryOverride?: boolean;
|
||||
// the only thing in here before was `telemetryOverride`, but we've removed it
|
||||
// keeping this interface around should we need it in the future.
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -145,7 +135,7 @@ interface SubmitEventProps {
|
||||
* Whether or not full telemetry sharing is enabled for the current client.
|
||||
*
|
||||
* It is **crucial** that this is the direct output of `useTelemetryState().shareFullTelemetry`,
|
||||
* regardless of other conditions that may affect whether we share it (such as event overrides).
|
||||
* regardless of other conditions that may affect whether we share it.
|
||||
*/
|
||||
shareFullTelemetry: boolean;
|
||||
/**
|
||||
@@ -173,8 +163,8 @@ interface SubmitEventProps {
|
||||
* If any of the following conditions are met, this will return and no data will be submitted:
|
||||
*
|
||||
* * If the app is in debug/development mode
|
||||
* * If a telemetry override is present, but it is not true
|
||||
* * If no telemetry override is present, and telemetry sharing is not true
|
||||
* * If the user's telemetry preference is not "full", we will only send pings
|
||||
* * If the user's telemetry preference is "none", we will never send any telemetry
|
||||
*
|
||||
* @privateRemarks
|
||||
* Telemetry sharing settings are never matched to `=== false`, but to `!== true` instead.
|
||||
@@ -186,27 +176,35 @@ interface SubmitEventProps {
|
||||
*/
|
||||
const submitPlausibleEvent = async ({ event, debugState, ...props }: SubmitEventProps) => {
|
||||
if (props.platformType === 'unknown') return;
|
||||
// if (debugState.enabled && debugState.shareFullTelemetry !== true) return;
|
||||
if (
|
||||
'plausibleOptions' in event && 'telemetryOverride' in event.plausibleOptions
|
||||
? event.plausibleOptions.telemetryOverride !== true
|
||||
: props.shareFullTelemetry !== true && event.type !== 'ping'
|
||||
// if the user's telemetry preference is not "full", we should only send pings
|
||||
props.shareFullTelemetry !== true &&
|
||||
event.type !== 'ping'
|
||||
)
|
||||
return;
|
||||
|
||||
// using a singleton this way instead of instantiating at file eval (first time it's imported)
|
||||
// because a user having "none" telemetry preference should mean Plausible never even initalizes
|
||||
plausibleInstance ??= Plausible({
|
||||
trackLocalhost: true,
|
||||
domain: props.platformType === 'mobile' ? MOBILE_DOMAIN : DOMAIN
|
||||
});
|
||||
|
||||
const fullEvent: PlausibleTrackerEvent = {
|
||||
eventName: event.type,
|
||||
props: {
|
||||
platform: props.platformType,
|
||||
fullTelemetry: props.shareFullTelemetry,
|
||||
coreVersion: props.buildInfo?.version ?? '0.1.0', // TODO(brxken128): clean this up
|
||||
commitHash: props.buildInfo?.commit ?? '0.1.0',
|
||||
// we used to fall back to '0.1.0' here, but we should never report an actual version number if we don't know
|
||||
coreVersion: props.buildInfo?.version ?? 'unknown',
|
||||
commitHash: props.buildInfo?.commit ?? 'unknown',
|
||||
debug: debugState.enabled
|
||||
},
|
||||
options: {
|
||||
domain: props.platformType === 'mobile' ? MOBILE_DOMAIN : DOMAIN,
|
||||
deviceWidth: props.screenWidth ?? window.screen.width,
|
||||
referrer: '',
|
||||
// by default do not track current URL, if it's provided in plausibleOptions, that will be sent
|
||||
url: '',
|
||||
...('plausibleOptions' in event ? event.plausibleOptions : undefined)
|
||||
},
|
||||
callback: debugState.telemetryLogging
|
||||
@@ -217,7 +215,7 @@ const submitPlausibleEvent = async ({ event, debugState, ...props }: SubmitEvent
|
||||
: undefined
|
||||
};
|
||||
|
||||
PlausibleProvider.trackEvent(
|
||||
plausibleInstance.trackEvent(
|
||||
fullEvent.eventName,
|
||||
{
|
||||
props: fullEvent.props,
|
||||
@@ -242,16 +240,10 @@ interface EventSubmissionCallbackProps {
|
||||
* The returned callback should only be fired once,
|
||||
* in order to prevent our analytics from being flooded.
|
||||
*
|
||||
* Certain events provide functionality to override the clients's telemetry sharing configuration.
|
||||
* This is not to ignore the user's choice, but because it should **only** be used in contexts where
|
||||
* telemetry sharing must be allowed/denied via external means.
|
||||
*
|
||||
* @remarks
|
||||
* If any of the following conditions are met, this will return and no data will be submitted:
|
||||
*
|
||||
* * If the app is in debug/development mode
|
||||
* * If a telemetry override is present, but it is not true
|
||||
* * If no telemetry override is present, and telemetry sharing is not true
|
||||
*
|
||||
* @returns a callback that, once executed, will submit the desired event
|
||||
*
|
||||
@@ -271,19 +263,20 @@ interface EventSubmissionCallbackProps {
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
export const usePlausibleEvent = () => {
|
||||
const debugState = useDebugState();
|
||||
export const usePlausibleEvent = (): ((props: EventSubmissionCallbackProps) => Promise<void>) => {
|
||||
const telemetryState = useTelemetryState();
|
||||
|
||||
const debugState = useDebugState();
|
||||
const previousEvent = useRef({} as BasePlausibleEvent<string>);
|
||||
|
||||
return useCallback(
|
||||
const sendPlausibleEvent = useCallback(
|
||||
async (props: EventSubmissionCallbackProps) => {
|
||||
if (previousEvent.current === props.event) return;
|
||||
else previousEvent.current = props.event;
|
||||
|
||||
submitPlausibleEvent({
|
||||
debugState,
|
||||
shareFullTelemetry: telemetryState.shareFullTelemetry,
|
||||
shareFullTelemetry: telemetryState.telemetryLevelPreference === 'full',
|
||||
platformType: telemetryState.platform,
|
||||
buildInfo: telemetryState.buildInfo,
|
||||
...props
|
||||
@@ -291,6 +284,10 @@ export const usePlausibleEvent = () => {
|
||||
},
|
||||
[debugState, telemetryState]
|
||||
);
|
||||
|
||||
if (telemetryState.telemetryLevelPreference === 'none') return async (...args: any[]) => {};
|
||||
|
||||
return sendPlausibleEvent;
|
||||
};
|
||||
|
||||
export interface PlausibleMonitorProps {
|
||||
@@ -366,7 +363,11 @@ export const usePlausiblePingMonitor = ({ currentPath }: PlausibleMonitorProps)
|
||||
}, [currentPath, plausibleEvent]);
|
||||
};
|
||||
|
||||
export const initPlausible = ({
|
||||
/**
|
||||
* Initializes the `platform` and `buildInfo` properties on `telemetryState` so they can be used
|
||||
* by Plausible if it's enabled.
|
||||
*/
|
||||
export const configureAnalyticsProperties = ({
|
||||
platformType,
|
||||
buildInfo
|
||||
}: {
|
||||
|
||||
@@ -4,6 +4,17 @@ import { type StoreNode } from 'solid-js/store';
|
||||
|
||||
type CreatePersistedMutableOpts<T> = {
|
||||
onSave?: (value: T) => T;
|
||||
/**
|
||||
* This function is always called after the data object's retrieval from localStorage and it getting assigned to the store.
|
||||
*
|
||||
* Originally intended for mutations, but can be used for other things if you have a reason to transform the data.
|
||||
*
|
||||
*
|
||||
* @note This is **not** called on initial load from default values.
|
||||
* @param value The existing data object from localStorage (or null if doesn't exist)
|
||||
* @returns The new data object
|
||||
*/
|
||||
onLoad?: (value: T | null) => T;
|
||||
};
|
||||
|
||||
// `@solid-primitives/storage`'s `makePersisted` doesn't support `solid-js/store`'s `createMutable` so we roll our own.
|
||||
@@ -12,12 +23,22 @@ export function createPersistedMutable<T extends StoreNode>(
|
||||
mutable: T,
|
||||
opts?: CreatePersistedMutableOpts<T>
|
||||
) {
|
||||
try {
|
||||
parsePersistedValue: try {
|
||||
const value = localStorage.getItem(key);
|
||||
if (value) {
|
||||
const persisted = JSON.parse(value);
|
||||
Object.assign(mutable, persisted);
|
||||
|
||||
if (value === null) {
|
||||
Object.assign(mutable, opts?.onLoad?.(value) ?? {});
|
||||
break parsePersistedValue;
|
||||
}
|
||||
|
||||
const persisted = JSON.parse(value);
|
||||
Object.assign(
|
||||
mutable,
|
||||
// if we have a function to use to transform data on load, use its return value
|
||||
opts?.onLoad?.(persisted) ??
|
||||
// otherwise just use the data from localStorage as is
|
||||
persisted
|
||||
);
|
||||
} catch (err) {
|
||||
console.error(`Error loading persisted state from localStorage key '${key}': ${err}`);
|
||||
}
|
||||
|
||||
@@ -2,16 +2,37 @@ import { createMutable } from 'solid-js/store';
|
||||
|
||||
import { ExplorerLayout } from '../core';
|
||||
import { createPersistedMutable, useSolidStore } from '../solid';
|
||||
import { isTelemetryStateV0 } from './telemetryState';
|
||||
|
||||
export const explorerLayout = createPersistedMutable(
|
||||
interface ExplorerLayoutStore {
|
||||
showPathBar: boolean;
|
||||
showTags: boolean;
|
||||
showImageSlider: boolean;
|
||||
defaultView: ExplorerLayout;
|
||||
}
|
||||
|
||||
export const explorerLayout = createPersistedMutable<ExplorerLayoutStore>(
|
||||
'sd-explorer-layout',
|
||||
createMutable({
|
||||
createMutable<ExplorerLayoutStore>({
|
||||
showPathBar: true,
|
||||
showTags: true,
|
||||
showImageSlider: true,
|
||||
// might move this to a store called settings
|
||||
defaultView: 'grid' as ExplorerLayout
|
||||
})
|
||||
}),
|
||||
{
|
||||
onLoad(value: unknown): ExplorerLayoutStore {
|
||||
// we had a bug previously where we saved the telemetry state in the wrong key
|
||||
// and we need to erase it from the layout store
|
||||
if (isTelemetryStateV0(value)) {
|
||||
// remove telemetry state from the layout store
|
||||
const { buildInfo, platform, shareFullTelemetry, ...rest } = value;
|
||||
return rest as ExplorerLayoutStore;
|
||||
}
|
||||
|
||||
return value as ExplorerLayoutStore;
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
export function useExplorerLayoutStore() {
|
||||
|
||||
102
packages/client/src/stores/telemetryState.ts
Normal file
102
packages/client/src/stores/telemetryState.ts
Normal file
@@ -0,0 +1,102 @@
|
||||
import { createMutable } from 'solid-js/store';
|
||||
|
||||
import { BuildInfo } from '../core';
|
||||
import { createPersistedMutable, useSolidStore } from '../solid';
|
||||
|
||||
/**
|
||||
* Possible Platform types that can be sourced from `usePlatform().platform` or even hardcoded.
|
||||
*
|
||||
* @remarks
|
||||
* The `tauri` platform is renamed to `desktop` for analytic purposes.
|
||||
*/
|
||||
export type PlausiblePlatformType = 'web' | 'mobile' | 'desktop' | 'unknown';
|
||||
|
||||
type TelemetryStateV0 = {
|
||||
shareFullTelemetry: boolean;
|
||||
platform: PlausiblePlatformType;
|
||||
buildInfo: BuildInfo | undefined;
|
||||
};
|
||||
|
||||
type TelemetryStateV1 = {
|
||||
telemetryLevelPreference: TelemetryLevelPreference;
|
||||
platform: PlausiblePlatformType;
|
||||
buildInfo: BuildInfo | undefined;
|
||||
stateVersion: 1;
|
||||
};
|
||||
|
||||
export type TelemetryLevelPreference = 'full' | 'minimal' | 'none';
|
||||
export const TELEMETRY_LEVEL_PREFERENCES = [
|
||||
'full',
|
||||
'minimal',
|
||||
'none'
|
||||
] satisfies TelemetryLevelPreference[];
|
||||
|
||||
const DEFAULT_TELEMETRY_STATE: TelemetryStateV1 = {
|
||||
telemetryLevelPreference: 'none',
|
||||
platform: 'unknown',
|
||||
buildInfo: undefined,
|
||||
stateVersion: 1
|
||||
};
|
||||
|
||||
export const telemetryState = createPersistedMutable<TelemetryStateV1>(
|
||||
'sd-telemetry-state',
|
||||
createMutable<TelemetryStateV1>(DEFAULT_TELEMETRY_STATE),
|
||||
{
|
||||
onLoad(value: unknown): TelemetryStateV1 {
|
||||
if (value === null) {
|
||||
// try to migrate from sd-explorer-layout key, was a bug for a while
|
||||
const oldData = localStorage.getItem('sd-explorer-layout');
|
||||
if (oldData === null) return DEFAULT_TELEMETRY_STATE;
|
||||
|
||||
// assign the old data to the current working value, and will fall through
|
||||
// to the v0/v1 migration logic
|
||||
value = JSON.parse(oldData);
|
||||
}
|
||||
|
||||
if (isTelemetryStateV1(value)) {
|
||||
return value;
|
||||
}
|
||||
if (isTelemetryStateV0(value)) {
|
||||
return migrateV0ToV1(value);
|
||||
}
|
||||
|
||||
// If the value is neither v0 nor v1, return the default state
|
||||
return DEFAULT_TELEMETRY_STATE;
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// exported because we use it in explorerLayout, there was a bug where we saved this into
|
||||
// the wrong key (sd-explorer-layout) and we need to erase it from the layout store
|
||||
export function isTelemetryStateV0(value: unknown): value is TelemetryStateV0 {
|
||||
return (
|
||||
typeof value === 'object' &&
|
||||
value !== null &&
|
||||
'shareFullTelemetry' in value &&
|
||||
'platform' in value &&
|
||||
'buildInfo' in value
|
||||
);
|
||||
}
|
||||
|
||||
function isTelemetryStateV1(value: unknown): value is TelemetryStateV1 {
|
||||
return (
|
||||
typeof value === 'object' &&
|
||||
value !== null &&
|
||||
'telemetryLevelPreference' in value &&
|
||||
'platform' in value &&
|
||||
'buildInfo' in value &&
|
||||
'stateVersion' in value &&
|
||||
value.stateVersion === 1
|
||||
);
|
||||
}
|
||||
|
||||
function migrateV0ToV1(value: TelemetryStateV0): TelemetryStateV1 {
|
||||
return {
|
||||
...DEFAULT_TELEMETRY_STATE,
|
||||
telemetryLevelPreference: value.shareFullTelemetry ? 'full' : 'minimal'
|
||||
};
|
||||
}
|
||||
|
||||
export function useTelemetryState() {
|
||||
return useSolidStore(telemetryState);
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
import { createMutable } from 'solid-js/store';
|
||||
|
||||
import { BuildInfo } from '../core';
|
||||
import { createPersistedMutable, useSolidStore } from '../solid';
|
||||
|
||||
/**
|
||||
* Possible Platform types that can be sourced from `usePlatform().platform` or even hardcoded.
|
||||
*
|
||||
* @remarks
|
||||
* The `tauri` platform is renamed to `desktop` for analytic purposes.
|
||||
*/
|
||||
export type PlausiblePlatformType = 'web' | 'mobile' | 'desktop' | 'unknown';
|
||||
|
||||
type TelemetryState = {
|
||||
shareFullTelemetry: boolean;
|
||||
platform: PlausiblePlatformType;
|
||||
buildInfo: BuildInfo | undefined;
|
||||
};
|
||||
|
||||
export const telemetryState = createPersistedMutable(
|
||||
'sd-explorer-layout',
|
||||
createMutable<TelemetryState>({
|
||||
shareFullTelemetry: false, // false by default
|
||||
platform: 'unknown',
|
||||
buildInfo: undefined
|
||||
})
|
||||
);
|
||||
|
||||
export function useTelemetryState() {
|
||||
return useSolidStore(telemetryState);
|
||||
}
|
||||
Reference in New Issue
Block a user