From 22f104ce792e33591d8916a3628b48844f6bb351 Mon Sep 17 00:00:00 2001 From: maliming Date: Wed, 13 May 2020 16:07:17 +0800 Subject: [PATCH 001/150] Add time zone infrastructure. --- .../ApplicationConfigurationDto.cs | 7 ++- .../Mvc/ApplicationConfigurations/ClockDto.cs | 7 +++ .../ApplicationConfigurations/TimingDto.cs | 24 ++++++++ .../AbpApplicationConfigurationAppService.cs | 40 +++++++++++++ .../Volo.Abp.Timing/Volo.Abp.Timing.csproj | 13 ++++- .../Volo/Abp/Timing/AbpTimingModule.cs | 22 +++++++- .../Volo/Abp/Timing/ITimezoneProvider.cs | 18 ++++++ .../Resources/AbpTiming/AbpTimingResource.cs | 10 ++++ .../Localization/Resources/AbpTiming/en.json | 7 +++ .../Localization/Resources/AbpTiming/tr.json | 7 +++ .../Resources/AbpTiming/zh-Hans.json | 7 +++ .../Resources/AbpTiming/zh-Hant.json | 7 +++ .../Abp/Timing/TZConvertTimezoneProvider.cs | 36 ++++++++++++ .../Volo/Abp/Timing/TimingSettingNames.cs | 7 +++ .../Volo/Abp/Timing/TimingSettingProvider.cs | 25 +++++++++ npm/packs/core/src/abp.js | 56 +++++++++++++++++++ 16 files changed, 290 insertions(+), 3 deletions(-) create mode 100644 framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ClockDto.cs create mode 100644 framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/TimingDto.cs create mode 100644 framework/src/Volo.Abp.Timing/Volo/Abp/Timing/ITimezoneProvider.cs create mode 100644 framework/src/Volo.Abp.Timing/Volo/Abp/Timing/Localization/Resources/AbpTiming/AbpTimingResource.cs create mode 100644 framework/src/Volo.Abp.Timing/Volo/Abp/Timing/Localization/Resources/AbpTiming/en.json create mode 100644 framework/src/Volo.Abp.Timing/Volo/Abp/Timing/Localization/Resources/AbpTiming/tr.json create mode 100644 framework/src/Volo.Abp.Timing/Volo/Abp/Timing/Localization/Resources/AbpTiming/zh-Hans.json create mode 100644 framework/src/Volo.Abp.Timing/Volo/Abp/Timing/Localization/Resources/AbpTiming/zh-Hant.json create mode 100644 framework/src/Volo.Abp.Timing/Volo/Abp/Timing/TZConvertTimezoneProvider.cs create mode 100644 framework/src/Volo.Abp.Timing/Volo/Abp/Timing/TimingSettingNames.cs create mode 100644 framework/src/Volo.Abp.Timing/Volo/Abp/Timing/TimingSettingProvider.cs diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ApplicationConfigurationDto.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ApplicationConfigurationDto.cs index da99a3e948..0001f56c1c 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ApplicationConfigurationDto.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ApplicationConfigurationDto.cs @@ -1,6 +1,7 @@ using System; using Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations.ObjectExtending; using Volo.Abp.AspNetCore.Mvc.MultiTenancy; +using Volo.Abp.Timing; namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations { @@ -21,6 +22,10 @@ namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations public CurrentTenantDto CurrentTenant { get; set; } + public TimingDto Timing { get; set; } + + public ClockDto Clock { get; set; } + public ObjectExtensionsDto ObjectExtensions { get; set; } } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ClockDto.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ClockDto.cs new file mode 100644 index 0000000000..2cff97f814 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/ClockDto.cs @@ -0,0 +1,7 @@ +namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations +{ + public class ClockDto + { + public string Kind { get; set; } + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/TimingDto.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/TimingDto.cs new file mode 100644 index 0000000000..bd95bfba0d --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/TimingDto.cs @@ -0,0 +1,24 @@ +namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations +{ + public class TimingDto + { + public TimeZone TimeZone { get; set; } + } + + public class TimeZone + { + public IanaTimeZone Iana { get; set; } + + public WindowsTimeZone Windows { get; set; } + } + + public class WindowsTimeZone + { + public string TimeZoneId { get; set; } + } + + public class IanaTimeZone + { + public string TimeZoneName { get; set; } + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/AbpApplicationConfigurationAppService.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/AbpApplicationConfigurationAppService.cs index a68a38b274..f68d4d1450 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/AbpApplicationConfigurationAppService.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/AbpApplicationConfigurationAppService.cs @@ -15,6 +15,7 @@ using Volo.Abp.Features; using Volo.Abp.Localization; using Volo.Abp.MultiTenancy; using Volo.Abp.Settings; +using Volo.Abp.Timing; using Volo.Abp.Users; namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations @@ -31,6 +32,8 @@ namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations private readonly ISettingDefinitionManager _settingDefinitionManager; private readonly IFeatureDefinitionManager _featureDefinitionManager; private readonly ILanguageProvider _languageProvider; + private readonly ITimezoneProvider _timezoneProvider; + private readonly AbpClockOptions _abpClockOptions; private readonly ICachedObjectExtensionsDtoService _cachedObjectExtensionsDtoService; public AbpApplicationConfigurationAppService( @@ -44,6 +47,8 @@ namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations ISettingDefinitionManager settingDefinitionManager, IFeatureDefinitionManager featureDefinitionManager, ILanguageProvider languageProvider, + ITimezoneProvider timezoneProvider, + IOptions abpClockOptions, ICachedObjectExtensionsDtoService cachedObjectExtensionsDtoService) { _serviceProvider = serviceProvider; @@ -54,6 +59,8 @@ namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations _settingDefinitionManager = settingDefinitionManager; _featureDefinitionManager = featureDefinitionManager; _languageProvider = languageProvider; + _timezoneProvider = timezoneProvider; + _abpClockOptions = abpClockOptions.Value; _cachedObjectExtensionsDtoService = cachedObjectExtensionsDtoService; _localizationOptions = localizationOptions.Value; _multiTenancyOptions = multiTenancyOptions.Value; @@ -74,6 +81,8 @@ namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations Setting = await GetSettingConfigAsync(), MultiTenancy = GetMultiTenancy(), CurrentTenant = GetCurrentTenant(), + Timing = await GetTimingConfigAsync(), + Clock = GetClockConfig(), ObjectExtensions = _cachedObjectExtensionsDtoService.Get() }; @@ -226,5 +235,36 @@ namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations return result; } + + protected virtual async Task GetTimingConfigAsync() + { + var result = new TimingDto(); + + var windowsTimeZoneId = await _settingProvider.GetOrNullAsync(TimingSettingNames.TimeZone); + if (!windowsTimeZoneId.IsNullOrWhiteSpace()) + { + result.TimeZone = new TimeZone + { + Windows = new WindowsTimeZone + { + TimeZoneId = windowsTimeZoneId + }, + Iana = new IanaTimeZone() + { + TimeZoneName = _timezoneProvider.WindowsToIana(windowsTimeZoneId) + } + }; + } + + return result; + } + + protected virtual ClockDto GetClockConfig() + { + return new ClockDto + { + Kind = Enum.GetName(typeof(DateTimeKind), _abpClockOptions.Kind) + }; + } } } diff --git a/framework/src/Volo.Abp.Timing/Volo.Abp.Timing.csproj b/framework/src/Volo.Abp.Timing/Volo.Abp.Timing.csproj index 4f9eb21f2f..53ef23fd62 100644 --- a/framework/src/Volo.Abp.Timing/Volo.Abp.Timing.csproj +++ b/framework/src/Volo.Abp.Timing/Volo.Abp.Timing.csproj @@ -14,8 +14,19 @@ + + + + + + + - + + + + + diff --git a/framework/src/Volo.Abp.Timing/Volo/Abp/Timing/AbpTimingModule.cs b/framework/src/Volo.Abp.Timing/Volo/Abp/Timing/AbpTimingModule.cs index d48cc01ef7..a306f3def9 100644 --- a/framework/src/Volo.Abp.Timing/Volo/Abp/Timing/AbpTimingModule.cs +++ b/framework/src/Volo.Abp.Timing/Volo/Abp/Timing/AbpTimingModule.cs @@ -1,9 +1,29 @@ -using Volo.Abp.Modularity; +using Volo.Abp.Localization; +using Volo.Abp.Localization.Resources.AbpLocalization; +using Volo.Abp.Modularity; +using Volo.Abp.Settings; +using Volo.Abp.Timing.Localization.Resources.AbpTiming; +using Volo.Abp.VirtualFileSystem; namespace Volo.Abp.Timing { + [DependsOn(typeof(AbpVirtualFileSystemModule), typeof(AbpSettingsModule))] public class AbpTimingModule : AbpModule { + public override void ConfigureServices(ServiceConfigurationContext context) + { + Configure(options => + { + options.FileSets.AddEmbedded("Volo.Abp", "Volo/Abp"); + }); + Configure(options => + { + options + .Resources + .Add("en") + .AddVirtualJson("/Timing/Localization/Resources/AbpTiming"); + }); + } } } diff --git a/framework/src/Volo.Abp.Timing/Volo/Abp/Timing/ITimezoneProvider.cs b/framework/src/Volo.Abp.Timing/Volo/Abp/Timing/ITimezoneProvider.cs new file mode 100644 index 0000000000..3ee1566803 --- /dev/null +++ b/framework/src/Volo.Abp.Timing/Volo/Abp/Timing/ITimezoneProvider.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; + +namespace Volo.Abp.Timing +{ + public interface ITimezoneProvider + { + List GetWindowsTimezones(); + + List GetIanaTimezones(); + + string WindowsToIana(string windowsTimeZoneId); + + string IanaToWindows(string ianaTimeZoneName); + + TimeZoneInfo GetTimeZoneInfo(string windowsOrIanaTimeZoneId); + } +} diff --git a/framework/src/Volo.Abp.Timing/Volo/Abp/Timing/Localization/Resources/AbpTiming/AbpTimingResource.cs b/framework/src/Volo.Abp.Timing/Volo/Abp/Timing/Localization/Resources/AbpTiming/AbpTimingResource.cs new file mode 100644 index 0000000000..71f32d17cb --- /dev/null +++ b/framework/src/Volo.Abp.Timing/Volo/Abp/Timing/Localization/Resources/AbpTiming/AbpTimingResource.cs @@ -0,0 +1,10 @@ +using Volo.Abp.Localization; + +namespace Volo.Abp.Timing.Localization.Resources.AbpTiming +{ + [LocalizationResourceName("AbpTiming")] + public class AbpTimingResource + { + + } +} diff --git a/framework/src/Volo.Abp.Timing/Volo/Abp/Timing/Localization/Resources/AbpTiming/en.json b/framework/src/Volo.Abp.Timing/Volo/Abp/Timing/Localization/Resources/AbpTiming/en.json new file mode 100644 index 0000000000..c017c3ebea --- /dev/null +++ b/framework/src/Volo.Abp.Timing/Volo/Abp/Timing/Localization/Resources/AbpTiming/en.json @@ -0,0 +1,7 @@ +{ + "culture": "en", + "texts": { + "DisplayName:Abp.Timing.Timezone": "Timezone", + "Description:Abp.Timing.Timezone": "Application time zone" + } +} diff --git a/framework/src/Volo.Abp.Timing/Volo/Abp/Timing/Localization/Resources/AbpTiming/tr.json b/framework/src/Volo.Abp.Timing/Volo/Abp/Timing/Localization/Resources/AbpTiming/tr.json new file mode 100644 index 0000000000..18139d6080 --- /dev/null +++ b/framework/src/Volo.Abp.Timing/Volo/Abp/Timing/Localization/Resources/AbpTiming/tr.json @@ -0,0 +1,7 @@ +{ + "culture": "tr", + "texts": { + "DisplayName:Abp.Timing.Timezone": "saat dilimi", + "Description:Abp.Timing.Timezone": "Uygulama saat dilimi" + } +} diff --git a/framework/src/Volo.Abp.Timing/Volo/Abp/Timing/Localization/Resources/AbpTiming/zh-Hans.json b/framework/src/Volo.Abp.Timing/Volo/Abp/Timing/Localization/Resources/AbpTiming/zh-Hans.json new file mode 100644 index 0000000000..41315bbfb0 --- /dev/null +++ b/framework/src/Volo.Abp.Timing/Volo/Abp/Timing/Localization/Resources/AbpTiming/zh-Hans.json @@ -0,0 +1,7 @@ +{ + "culture": "zh-Hans", + "texts": { + "DisplayName:Abp.Timing.Timezone": "时区", + "Description:Abp.Timing.Timezone": "应用程序的时区" + } +} diff --git a/framework/src/Volo.Abp.Timing/Volo/Abp/Timing/Localization/Resources/AbpTiming/zh-Hant.json b/framework/src/Volo.Abp.Timing/Volo/Abp/Timing/Localization/Resources/AbpTiming/zh-Hant.json new file mode 100644 index 0000000000..5836792e3d --- /dev/null +++ b/framework/src/Volo.Abp.Timing/Volo/Abp/Timing/Localization/Resources/AbpTiming/zh-Hant.json @@ -0,0 +1,7 @@ +{ + "culture": "zh-Hant", + "texts": { + "DisplayName:Abp.Timing.Timezone": "時區", + "Description:Abp.Timing.Timezone": "應用程序的時區" + } +} diff --git a/framework/src/Volo.Abp.Timing/Volo/Abp/Timing/TZConvertTimezoneProvider.cs b/framework/src/Volo.Abp.Timing/Volo/Abp/Timing/TZConvertTimezoneProvider.cs new file mode 100644 index 0000000000..551b26e488 --- /dev/null +++ b/framework/src/Volo.Abp.Timing/Volo/Abp/Timing/TZConvertTimezoneProvider.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using TimeZoneConverter; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.Timing +{ + public class TZConvertTimezoneProvider : ITimezoneProvider, ITransientDependency + { + public virtual List GetWindowsTimezones() + { + return TZConvert.KnownIanaTimeZoneNames.OrderBy(x => x).Select(x => new NameValue(x, x)).ToList(); + } + + public virtual List GetIanaTimezones() + { + return TZConvert.KnownIanaTimeZoneNames.OrderBy(x => x).Select(x => new NameValue(x, x)).ToList(); + } + + public virtual string WindowsToIana(string windowsTimeZoneId) + { + return TZConvert.WindowsToIana(windowsTimeZoneId); + } + + public virtual string IanaToWindows(string ianaTimeZoneName) + { + return TZConvert.IanaToWindows(ianaTimeZoneName); + } + + public virtual TimeZoneInfo GetTimeZoneInfo(string windowsOrIanaTimeZoneId) + { + return TZConvert.GetTimeZoneInfo(windowsOrIanaTimeZoneId); + } + } +} diff --git a/framework/src/Volo.Abp.Timing/Volo/Abp/Timing/TimingSettingNames.cs b/framework/src/Volo.Abp.Timing/Volo/Abp/Timing/TimingSettingNames.cs new file mode 100644 index 0000000000..15ba1bc9f9 --- /dev/null +++ b/framework/src/Volo.Abp.Timing/Volo/Abp/Timing/TimingSettingNames.cs @@ -0,0 +1,7 @@ +namespace Volo.Abp.Timing +{ + public static class TimingSettingNames + { + public const string TimeZone = "Abp.Timing.TimeZone"; + } +} diff --git a/framework/src/Volo.Abp.Timing/Volo/Abp/Timing/TimingSettingProvider.cs b/framework/src/Volo.Abp.Timing/Volo/Abp/Timing/TimingSettingProvider.cs new file mode 100644 index 0000000000..c2ebf73cfd --- /dev/null +++ b/framework/src/Volo.Abp.Timing/Volo/Abp/Timing/TimingSettingProvider.cs @@ -0,0 +1,25 @@ +using Volo.Abp.Localization; +using Volo.Abp.Settings; +using Volo.Abp.Timing.Localization.Resources.AbpTiming; + +namespace Volo.Abp.Timing +{ + public class TimingSettingProvider : SettingDefinitionProvider + { + public override void Define(ISettingDefinitionContext context) + { + context.Add( + new SettingDefinition(TimingSettingNames.TimeZone, + "UTC", + L("DisplayName:Abp.Timing.Timezone"), + L("Description:Abp.Timing.Timezone"), + isVisibleToClients: true) + ); + } + + private static LocalizableString L(string name) + { + return LocalizableString.Create(name); + } + } +} diff --git a/npm/packs/core/src/abp.js b/npm/packs/core/src/abp.js index fa42828306..722bd5f4a0 100644 --- a/npm/packs/core/src/abp.js +++ b/npm/packs/core/src/abp.js @@ -663,4 +663,60 @@ var abp = abp || {}; return abp.utils.getCookieValue(abp.security.antiForgery.tokenCookieName); }; + /* CLOCK *****************************************/ + abp.clock = abp.clock || {}; + + abp.clock.kind = 'Unspecified'; + + abp.clock.supportsMultipleTimezone = function () { + return abp.clock.kind === 'Utc'; + }; + + var toLocal = function (date) { + return new Date( + date.getUTCFullYear(), + date.getUTCMonth(), + date.getUTCDate(), + date.getUTCHours(), + date.getUTCMinutes(), + date.getUTCSeconds(), + date.getUTCMilliseconds() + ); + }; + + var toUtc = function (date) { + Date.UTC( + date.getUTCFullYear(), + date.getUTCMonth(), + date.getUTCDate(), + date.getUTCHours(), + date.getUTCMinutes(), + date.getUTCSeconds(), + date.getUTCMilliseconds() + ); + }; + + abp.clock.now = function () { + if (abp.clock.kind === 'Utc') { + return toUtc(new Date()); + } + return new Date(); + }; + + abp.clock.normalize = function (date) { + var kind = abp.clock.kind; + + if (kind === 'Unspecified') { + return date; + } + + if (kind === 'Local') { + return toLocal(new Date()); + } + + if (kind === 'Utc') { + return toUtc(new Date()); + } + }; + })(); \ No newline at end of file From 055ffa90cc79ba98744080920ddf3275841b9ead Mon Sep 17 00:00:00 2001 From: maliming Date: Thu, 14 May 2020 10:49:12 +0800 Subject: [PATCH 002/150] Update abp.js --- npm/packs/core/src/abp.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/npm/packs/core/src/abp.js b/npm/packs/core/src/abp.js index 722bd5f4a0..190143da03 100644 --- a/npm/packs/core/src/abp.js +++ b/npm/packs/core/src/abp.js @@ -711,12 +711,12 @@ var abp = abp || {}; } if (kind === 'Local') { - return toLocal(new Date()); + return toLocal(date); } if (kind === 'Utc') { - return toUtc(new Date()); + return toUtc(date); } }; -})(); \ No newline at end of file +})(); From ce58bade8af580e1ee2293c4a2c1381a038e48ac Mon Sep 17 00:00:00 2001 From: liangshiwei Date: Fri, 15 May 2020 22:47:15 +0800 Subject: [PATCH 003/150] Build virtual file explorer module --- .../Volo.Abp.VirtualFileExplorer.sln | 30 + ...lo.Abp.VirtualFileExplorer.sln.DotSettings | 23 + modules/virtual-file-explorer/common.props | 12 + .../AbpVirtualFileExplorerOptions.cs | 10 + .../AbpVirtualFileExplorerWebModule.cs | 53 + .../Localization/Resources/cs.json | 6 + .../Localization/Resources/en.json | 12 + .../Localization/Resources/pl-PL.json | 6 + .../Localization/Resources/pt-BR.json | 6 + .../Localization/Resources/sl.json | 6 + .../Localization/Resources/tr.json | 6 + .../Localization/Resources/vi.json | 6 + .../Localization/Resources/zh-Hans.json | 12 + .../Localization/Resources/zh-Hant.json | 6 + .../VirtualFileExplorerResource.cs | 10 + .../Models/FileInfoViewModel.cs | 21 + .../AbpVirtualFileExplorerMenuContributor.cs | 25 + .../VirtualFileExplorerMenuNames.cs | 9 + .../FileContentModal.cshtml | 26 + .../FileContentModal.cshtml.cs | 39 + .../Pages/VirtualFileExplorer/Index.cshtml | 94 ++ .../Pages/VirtualFileExplorer/Index.cshtml.cs | 106 ++ .../VirtualFileExplorerPageModel.cs | 16 + .../VirtualFileExplorer/_ViewImports.cshtml | 4 + .../Pages/VirtualFileExplorer/clipboard.js | 978 +++++++++++++ .../Pages/VirtualFileExplorer/index.css | 3 + .../Pages/VirtualFileExplorer/index.js | 7 + .../Pages/VirtualFileExplorer/prism.css | 195 +++ .../Pages/VirtualFileExplorer/prism.js | 1275 +++++++++++++++++ .../Properties/launchSettings.json | 27 + .../VirtualFileExplorerConsts.cs | 7 + .../Volo.Abp.VirtualFileExplorer.Web.csproj | 34 + 32 files changed, 3070 insertions(+) create mode 100644 modules/virtual-file-explorer/Volo.Abp.VirtualFileExplorer.sln create mode 100644 modules/virtual-file-explorer/Volo.Abp.VirtualFileExplorer.sln.DotSettings create mode 100644 modules/virtual-file-explorer/common.props create mode 100644 modules/virtual-file-explorer/src/Volo.Abp.VirtualFileExplorer.Web/AbpVirtualFileExplorerOptions.cs create mode 100644 modules/virtual-file-explorer/src/Volo.Abp.VirtualFileExplorer.Web/AbpVirtualFileExplorerWebModule.cs create mode 100644 modules/virtual-file-explorer/src/Volo.Abp.VirtualFileExplorer.Web/Localization/Resources/cs.json create mode 100644 modules/virtual-file-explorer/src/Volo.Abp.VirtualFileExplorer.Web/Localization/Resources/en.json create mode 100644 modules/virtual-file-explorer/src/Volo.Abp.VirtualFileExplorer.Web/Localization/Resources/pl-PL.json create mode 100644 modules/virtual-file-explorer/src/Volo.Abp.VirtualFileExplorer.Web/Localization/Resources/pt-BR.json create mode 100644 modules/virtual-file-explorer/src/Volo.Abp.VirtualFileExplorer.Web/Localization/Resources/sl.json create mode 100644 modules/virtual-file-explorer/src/Volo.Abp.VirtualFileExplorer.Web/Localization/Resources/tr.json create mode 100644 modules/virtual-file-explorer/src/Volo.Abp.VirtualFileExplorer.Web/Localization/Resources/vi.json create mode 100644 modules/virtual-file-explorer/src/Volo.Abp.VirtualFileExplorer.Web/Localization/Resources/zh-Hans.json create mode 100644 modules/virtual-file-explorer/src/Volo.Abp.VirtualFileExplorer.Web/Localization/Resources/zh-Hant.json create mode 100644 modules/virtual-file-explorer/src/Volo.Abp.VirtualFileExplorer.Web/Localization/VirtualFileExplorerResource.cs create mode 100644 modules/virtual-file-explorer/src/Volo.Abp.VirtualFileExplorer.Web/Models/FileInfoViewModel.cs create mode 100644 modules/virtual-file-explorer/src/Volo.Abp.VirtualFileExplorer.Web/Navigation/AbpVirtualFileExplorerMenuContributor.cs create mode 100644 modules/virtual-file-explorer/src/Volo.Abp.VirtualFileExplorer.Web/Navigation/VirtualFileExplorerMenuNames.cs create mode 100644 modules/virtual-file-explorer/src/Volo.Abp.VirtualFileExplorer.Web/Pages/VirtualFileExplorer/FileContentModal.cshtml create mode 100644 modules/virtual-file-explorer/src/Volo.Abp.VirtualFileExplorer.Web/Pages/VirtualFileExplorer/FileContentModal.cshtml.cs create mode 100644 modules/virtual-file-explorer/src/Volo.Abp.VirtualFileExplorer.Web/Pages/VirtualFileExplorer/Index.cshtml create mode 100644 modules/virtual-file-explorer/src/Volo.Abp.VirtualFileExplorer.Web/Pages/VirtualFileExplorer/Index.cshtml.cs create mode 100644 modules/virtual-file-explorer/src/Volo.Abp.VirtualFileExplorer.Web/Pages/VirtualFileExplorer/VirtualFileExplorerPageModel.cs create mode 100644 modules/virtual-file-explorer/src/Volo.Abp.VirtualFileExplorer.Web/Pages/VirtualFileExplorer/_ViewImports.cshtml create mode 100644 modules/virtual-file-explorer/src/Volo.Abp.VirtualFileExplorer.Web/Pages/VirtualFileExplorer/clipboard.js create mode 100644 modules/virtual-file-explorer/src/Volo.Abp.VirtualFileExplorer.Web/Pages/VirtualFileExplorer/index.css create mode 100644 modules/virtual-file-explorer/src/Volo.Abp.VirtualFileExplorer.Web/Pages/VirtualFileExplorer/index.js create mode 100644 modules/virtual-file-explorer/src/Volo.Abp.VirtualFileExplorer.Web/Pages/VirtualFileExplorer/prism.css create mode 100644 modules/virtual-file-explorer/src/Volo.Abp.VirtualFileExplorer.Web/Pages/VirtualFileExplorer/prism.js create mode 100644 modules/virtual-file-explorer/src/Volo.Abp.VirtualFileExplorer.Web/Properties/launchSettings.json create mode 100644 modules/virtual-file-explorer/src/Volo.Abp.VirtualFileExplorer.Web/VirtualFileExplorerConsts.cs create mode 100644 modules/virtual-file-explorer/src/Volo.Abp.VirtualFileExplorer.Web/Volo.Abp.VirtualFileExplorer.Web.csproj diff --git a/modules/virtual-file-explorer/Volo.Abp.VirtualFileExplorer.sln b/modules/virtual-file-explorer/Volo.Abp.VirtualFileExplorer.sln new file mode 100644 index 0000000000..f75ca70869 --- /dev/null +++ b/modules/virtual-file-explorer/Volo.Abp.VirtualFileExplorer.sln @@ -0,0 +1,30 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.29001.49 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{649A3FFA-182F-4E56-9717-E6A9A2BEC545}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.VirtualFileExplorer.Web", "src\Volo.Abp.VirtualFileExplorer.Web\Volo.Abp.VirtualFileExplorer.Web.csproj", "{3B7B6317-1B85-4164-8E11-75574F80AE17}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {3B7B6317-1B85-4164-8E11-75574F80AE17}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3B7B6317-1B85-4164-8E11-75574F80AE17}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3B7B6317-1B85-4164-8E11-75574F80AE17}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3B7B6317-1B85-4164-8E11-75574F80AE17}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {3B7B6317-1B85-4164-8E11-75574F80AE17} = {649A3FFA-182F-4E56-9717-E6A9A2BEC545} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {4324B3B4-B60B-4E3C-91D8-59576B4E26DD} + EndGlobalSection +EndGlobal diff --git a/modules/virtual-file-explorer/Volo.Abp.VirtualFileExplorer.sln.DotSettings b/modules/virtual-file-explorer/Volo.Abp.VirtualFileExplorer.sln.DotSettings new file mode 100644 index 0000000000..cb0b2c919f --- /dev/null +++ b/modules/virtual-file-explorer/Volo.Abp.VirtualFileExplorer.sln.DotSettings @@ -0,0 +1,23 @@ + + True + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + Required + Required + Required + Required + False + True + False + False + True + False + False + SQL + \ No newline at end of file diff --git a/modules/virtual-file-explorer/common.props b/modules/virtual-file-explorer/common.props new file mode 100644 index 0000000000..4adb69f0fe --- /dev/null +++ b/modules/virtual-file-explorer/common.props @@ -0,0 +1,12 @@ + + + latest + 0.1.0 + $(NoWarn);CS1591 + + + + + + + \ No newline at end of file diff --git a/modules/virtual-file-explorer/src/Volo.Abp.VirtualFileExplorer.Web/AbpVirtualFileExplorerOptions.cs b/modules/virtual-file-explorer/src/Volo.Abp.VirtualFileExplorer.Web/AbpVirtualFileExplorerOptions.cs new file mode 100644 index 0000000000..2dabdf3af9 --- /dev/null +++ b/modules/virtual-file-explorer/src/Volo.Abp.VirtualFileExplorer.Web/AbpVirtualFileExplorerOptions.cs @@ -0,0 +1,10 @@ +namespace Volo.Abp.VirtualFileExplorer.Web +{ + public class AbpVirtualFileExplorerOptions + { + /// + /// Default: true. + /// + public bool IsEnabled { get; set; } = true; + } +} diff --git a/modules/virtual-file-explorer/src/Volo.Abp.VirtualFileExplorer.Web/AbpVirtualFileExplorerWebModule.cs b/modules/virtual-file-explorer/src/Volo.Abp.VirtualFileExplorer.Web/AbpVirtualFileExplorerWebModule.cs new file mode 100644 index 0000000000..fdb40076fb --- /dev/null +++ b/modules/virtual-file-explorer/src/Volo.Abp.VirtualFileExplorer.Web/AbpVirtualFileExplorerWebModule.cs @@ -0,0 +1,53 @@ +using Microsoft.AspNetCore.Mvc.RazorPages; +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap; +using Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared; +using Volo.Abp.Localization; +using Volo.Abp.Modularity; +using Volo.Abp.UI.Navigation; +using Volo.Abp.Validation.Localization; +using Volo.Abp.VirtualFileExplorer.Localization; +using Volo.Abp.VirtualFileExplorer.Web.Navigation; +using Volo.Abp.VirtualFileSystem; + +namespace Volo.Abp.VirtualFileExplorer.Web +{ + [DependsOn(typeof(AbpAspNetCoreMvcUiBootstrapModule))] + [DependsOn(typeof(AbpAspNetCoreMvcUiThemeSharedModule))] + public class AbpVirtualFileExplorerWebModule : AbpModule + { + public override void PreConfigureServices(ServiceConfigurationContext context) + { + PreConfigure(mvcBuilder => + { + mvcBuilder.AddApplicationPartIfNotExists(typeof(AbpVirtualFileExplorerWebModule).Assembly); + }); + } + + public override void ConfigureServices(ServiceConfigurationContext context) + { + var virtualFileExplorerOptions = context.Services.ExecutePreConfiguredActions(); + + if (virtualFileExplorerOptions.IsEnabled) + { + Configure(options => + { + options.MenuContributors.Add(new VirtualFileExplorerMenuContributor()); + }); + + Configure(options => + { + options.FileSets.AddEmbedded("Volo.Abp.VirtualFileExplorer.Web"); + }); + + Configure(options => + { + options.Resources + .Add("en") + .AddBaseTypes(typeof(AbpValidationResource)) + .AddVirtualJson("/Localization/Resources"); + }); + } + } + } +} diff --git a/modules/virtual-file-explorer/src/Volo.Abp.VirtualFileExplorer.Web/Localization/Resources/cs.json b/modules/virtual-file-explorer/src/Volo.Abp.VirtualFileExplorer.Web/Localization/Resources/cs.json new file mode 100644 index 0000000000..6a060a54c1 --- /dev/null +++ b/modules/virtual-file-explorer/src/Volo.Abp.VirtualFileExplorer.Web/Localization/Resources/cs.json @@ -0,0 +1,6 @@ +{ + "culture": "cs", + "texts": { + + } +} \ No newline at end of file diff --git a/modules/virtual-file-explorer/src/Volo.Abp.VirtualFileExplorer.Web/Localization/Resources/en.json b/modules/virtual-file-explorer/src/Volo.Abp.VirtualFileExplorer.Web/Localization/Resources/en.json new file mode 100644 index 0000000000..b2c29d15ad --- /dev/null +++ b/modules/virtual-file-explorer/src/Volo.Abp.VirtualFileExplorer.Web/Localization/Resources/en.json @@ -0,0 +1,12 @@ +{ + "culture": "en", + "texts": { + "VirtualFileExplorer" : "Virtual file explorer", + "VirtualFileType" : "Virtual file type", + "Menu:VirtualFileExplorer" : "Virtual file explorer", + "LastUpdateTime" : "Last update time", + "VirtualFileName" : "Virtual file name", + "FileContent" : "File content", + "Size" : "Size" + } +} diff --git a/modules/virtual-file-explorer/src/Volo.Abp.VirtualFileExplorer.Web/Localization/Resources/pl-PL.json b/modules/virtual-file-explorer/src/Volo.Abp.VirtualFileExplorer.Web/Localization/Resources/pl-PL.json new file mode 100644 index 0000000000..3ea7b190ee --- /dev/null +++ b/modules/virtual-file-explorer/src/Volo.Abp.VirtualFileExplorer.Web/Localization/Resources/pl-PL.json @@ -0,0 +1,6 @@ +{ + "culture": "pl-PL", + "texts": { + + } +} \ No newline at end of file diff --git a/modules/virtual-file-explorer/src/Volo.Abp.VirtualFileExplorer.Web/Localization/Resources/pt-BR.json b/modules/virtual-file-explorer/src/Volo.Abp.VirtualFileExplorer.Web/Localization/Resources/pt-BR.json new file mode 100644 index 0000000000..6d746df04c --- /dev/null +++ b/modules/virtual-file-explorer/src/Volo.Abp.VirtualFileExplorer.Web/Localization/Resources/pt-BR.json @@ -0,0 +1,6 @@ +{ + "culture": "pt-BR", + "texts": { + + } +} \ No newline at end of file diff --git a/modules/virtual-file-explorer/src/Volo.Abp.VirtualFileExplorer.Web/Localization/Resources/sl.json b/modules/virtual-file-explorer/src/Volo.Abp.VirtualFileExplorer.Web/Localization/Resources/sl.json new file mode 100644 index 0000000000..9d33bd8201 --- /dev/null +++ b/modules/virtual-file-explorer/src/Volo.Abp.VirtualFileExplorer.Web/Localization/Resources/sl.json @@ -0,0 +1,6 @@ +{ + "culture": "sl", + "texts": { + + } +} diff --git a/modules/virtual-file-explorer/src/Volo.Abp.VirtualFileExplorer.Web/Localization/Resources/tr.json b/modules/virtual-file-explorer/src/Volo.Abp.VirtualFileExplorer.Web/Localization/Resources/tr.json new file mode 100644 index 0000000000..febe7634a4 --- /dev/null +++ b/modules/virtual-file-explorer/src/Volo.Abp.VirtualFileExplorer.Web/Localization/Resources/tr.json @@ -0,0 +1,6 @@ +{ + "culture": "tr", + "texts": { + + } +} diff --git a/modules/virtual-file-explorer/src/Volo.Abp.VirtualFileExplorer.Web/Localization/Resources/vi.json b/modules/virtual-file-explorer/src/Volo.Abp.VirtualFileExplorer.Web/Localization/Resources/vi.json new file mode 100644 index 0000000000..d8eb5f3c75 --- /dev/null +++ b/modules/virtual-file-explorer/src/Volo.Abp.VirtualFileExplorer.Web/Localization/Resources/vi.json @@ -0,0 +1,6 @@ +{ + "culture": "vi", + "texts": { + + } +} diff --git a/modules/virtual-file-explorer/src/Volo.Abp.VirtualFileExplorer.Web/Localization/Resources/zh-Hans.json b/modules/virtual-file-explorer/src/Volo.Abp.VirtualFileExplorer.Web/Localization/Resources/zh-Hans.json new file mode 100644 index 0000000000..02d40f88c6 --- /dev/null +++ b/modules/virtual-file-explorer/src/Volo.Abp.VirtualFileExplorer.Web/Localization/Resources/zh-Hans.json @@ -0,0 +1,12 @@ +{ + "culture": "zh-Hans", + "texts": { + "VirtualFileExplorer" : "虚拟文件浏览器", + "VirtualFileType" : "虚拟文件类型", + "Menu:VirtualFileExplorer" : "虚拟文件浏览器", + "LastUpdateTime" : "最后更新时间", + "VirtualFileName" : "虚拟文件名称", + "FileContent" : "文件内容", + "Size" : "文件大小" + } +} diff --git a/modules/virtual-file-explorer/src/Volo.Abp.VirtualFileExplorer.Web/Localization/Resources/zh-Hant.json b/modules/virtual-file-explorer/src/Volo.Abp.VirtualFileExplorer.Web/Localization/Resources/zh-Hant.json new file mode 100644 index 0000000000..52291dead1 --- /dev/null +++ b/modules/virtual-file-explorer/src/Volo.Abp.VirtualFileExplorer.Web/Localization/Resources/zh-Hant.json @@ -0,0 +1,6 @@ +{ + "culture": "zh-Hant", + "texts": { + + } +} diff --git a/modules/virtual-file-explorer/src/Volo.Abp.VirtualFileExplorer.Web/Localization/VirtualFileExplorerResource.cs b/modules/virtual-file-explorer/src/Volo.Abp.VirtualFileExplorer.Web/Localization/VirtualFileExplorerResource.cs new file mode 100644 index 0000000000..af007c506b --- /dev/null +++ b/modules/virtual-file-explorer/src/Volo.Abp.VirtualFileExplorer.Web/Localization/VirtualFileExplorerResource.cs @@ -0,0 +1,10 @@ +using Volo.Abp.Localization; + +namespace Volo.Abp.VirtualFileExplorer.Localization +{ + [LocalizationResourceName("AbpVirtualFileExplorer")] + public class VirtualFileExplorerResource + { + + } +} diff --git a/modules/virtual-file-explorer/src/Volo.Abp.VirtualFileExplorer.Web/Models/FileInfoViewModel.cs b/modules/virtual-file-explorer/src/Volo.Abp.VirtualFileExplorer.Web/Models/FileInfoViewModel.cs new file mode 100644 index 0000000000..43f2e57797 --- /dev/null +++ b/modules/virtual-file-explorer/src/Volo.Abp.VirtualFileExplorer.Web/Models/FileInfoViewModel.cs @@ -0,0 +1,21 @@ +using System; + +namespace Volo.Abp.VirtualFileExplorer.Web.Models +{ + public class FileInfoViewModel + { + public string FilePath { get; set; } + + public string Icon { get; set; } + + public string FileType { get; set; } + + public string Length { get; set; } + + public string FileName { get; set; } + + public DateTime LastUpdateTime { get; set; } + + public bool IsDirectory { get; set; } + } +} diff --git a/modules/virtual-file-explorer/src/Volo.Abp.VirtualFileExplorer.Web/Navigation/AbpVirtualFileExplorerMenuContributor.cs b/modules/virtual-file-explorer/src/Volo.Abp.VirtualFileExplorer.Web/Navigation/AbpVirtualFileExplorerMenuContributor.cs new file mode 100644 index 0000000000..b0f866b5cd --- /dev/null +++ b/modules/virtual-file-explorer/src/Volo.Abp.VirtualFileExplorer.Web/Navigation/AbpVirtualFileExplorerMenuContributor.cs @@ -0,0 +1,25 @@ +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Localization; +using Volo.Abp.UI.Navigation; +using Volo.Abp.VirtualFileExplorer.Localization; + +namespace Volo.Abp.VirtualFileExplorer.Web.Navigation +{ + public class VirtualFileExplorerMenuContributor : IMenuContributor + { + public virtual Task ConfigureMenuAsync(MenuConfigurationContext context) + { + if (context.Menu.Name != StandardMenus.Main) + { + return Task.CompletedTask; + } + + var l = context.ServiceProvider.GetRequiredService>(); + + context.Menu.Items.Add(new ApplicationMenuItem(VirtualFileExplorerMenuNames.Index, l["Menu:VirtualFileExplorer"], icon: "fa fa-file", url: "/VirtualFileExplorer")); + + return Task.CompletedTask; + } + } +} diff --git a/modules/virtual-file-explorer/src/Volo.Abp.VirtualFileExplorer.Web/Navigation/VirtualFileExplorerMenuNames.cs b/modules/virtual-file-explorer/src/Volo.Abp.VirtualFileExplorer.Web/Navigation/VirtualFileExplorerMenuNames.cs new file mode 100644 index 0000000000..bcb87d2578 --- /dev/null +++ b/modules/virtual-file-explorer/src/Volo.Abp.VirtualFileExplorer.Web/Navigation/VirtualFileExplorerMenuNames.cs @@ -0,0 +1,9 @@ +namespace Volo.Abp.VirtualFileExplorer.Web.Navigation +{ + public class VirtualFileExplorerMenuNames + { + public const string GroupName = "AbpVirualFileExplorer"; + + public const string Index = GroupName + ".Index"; + } +} diff --git a/modules/virtual-file-explorer/src/Volo.Abp.VirtualFileExplorer.Web/Pages/VirtualFileExplorer/FileContentModal.cshtml b/modules/virtual-file-explorer/src/Volo.Abp.VirtualFileExplorer.Web/Pages/VirtualFileExplorer/FileContentModal.cshtml new file mode 100644 index 0000000000..d212cf7c1c --- /dev/null +++ b/modules/virtual-file-explorer/src/Volo.Abp.VirtualFileExplorer.Web/Pages/VirtualFileExplorer/FileContentModal.cshtml @@ -0,0 +1,26 @@ +@page +@using Microsoft.AspNetCore.Mvc.Localization +@using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Modal +@using Volo.Abp.VirtualFileExplorer.Localization +@using Volo.Abp.VirtualFileExplorer.Web.Pages.VirtualFileExplorer +@model Volo.Abp.VirtualFileExplorer.Web.Pages.VirtualFileExplorer.FileContentModal +@inject IHtmlLocalizer L +@{ + Layout = null; +} + + + + + + + + +
+            
+@Model.Content.Trim()
+		   
+    
+
+ +
diff --git a/modules/virtual-file-explorer/src/Volo.Abp.VirtualFileExplorer.Web/Pages/VirtualFileExplorer/FileContentModal.cshtml.cs b/modules/virtual-file-explorer/src/Volo.Abp.VirtualFileExplorer.Web/Pages/VirtualFileExplorer/FileContentModal.cshtml.cs new file mode 100644 index 0000000000..81e09b5cd2 --- /dev/null +++ b/modules/virtual-file-explorer/src/Volo.Abp.VirtualFileExplorer.Web/Pages/VirtualFileExplorer/FileContentModal.cshtml.cs @@ -0,0 +1,39 @@ +using System; +using System.ComponentModel.DataAnnotations; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.RazorPages; +using Microsoft.Extensions.FileProviders; +using Volo.Abp.VirtualFileSystem; + +namespace Volo.Abp.VirtualFileExplorer.Web.Pages.VirtualFileExplorer +{ + public class FileContentModal : PageModel + { + [Required] + [BindProperty(SupportsGet = true)] + public string FilePath { get; set; } + + public string Content { get; set; } + + protected IVirtualFileProvider VirtualFileProvider { get; } + + public FileContentModal(IVirtualFileProvider virtualFileProvider) + { + VirtualFileProvider = virtualFileProvider; + } + + public virtual async Task OnGetAsync() + { + var fileInfo = VirtualFileProvider.GetFileInfo(FilePath); + if (fileInfo == null || fileInfo.IsDirectory) + { + return NotFound(); + } + + Content = await fileInfo.ReadAsStringAsync(); + + return Page(); + } + } +} diff --git a/modules/virtual-file-explorer/src/Volo.Abp.VirtualFileExplorer.Web/Pages/VirtualFileExplorer/Index.cshtml b/modules/virtual-file-explorer/src/Volo.Abp.VirtualFileExplorer.Web/Pages/VirtualFileExplorer/Index.cshtml new file mode 100644 index 0000000000..41b7ba1405 --- /dev/null +++ b/modules/virtual-file-explorer/src/Volo.Abp.VirtualFileExplorer.Web/Pages/VirtualFileExplorer/Index.cshtml @@ -0,0 +1,94 @@ +@page +@using Localization.Resources.AbpUi +@using Microsoft.AspNetCore.Mvc.Localization +@using Volo.Abp.AspNetCore.Mvc.UI.Layout +@using Volo.Abp.VirtualFileExplorer.Localization +@using Volo.Abp.VirtualFileExplorer.Web.Navigation +@using Volo.Abp.VirtualFileExplorer.Web.Pages.VirtualFileExplorer +@using Volo.Abp.VirtualFileSystem.Embedded +@model IndexModel +@inject IPageLayout PageLayout +@inject IHtmlLocalizer L +@inject IHtmlLocalizer UiLocalizer +@{ + PageLayout.Content.Title = L["VirtualFileExplorer"].Value; + PageLayout.Content.BreadCrumb.Add(L["Menu:VirtualFileExplorer"].Value); + PageLayout.Content.MenuItemName = VirtualFileExplorerMenuNames.Index; +} + +@section styles { + + + + +} + +@section scripts { + + + + +} + + + + + + @L["VirtualFileExplorer"] + + + + + + @Html.Raw(Model.PathNavigation) + + + + + @UiLocalizer["Actions"] + @L["VirtualFileName"] + @L["VirtualFileType"] + @L["LastUpdateTime"] + @L["Size"] + + + + + @{ + foreach (var item in Model.FileInfoList) + { + + + @if (!item.IsDirectory) + { + + } + else + { + / + } + + @Html.Raw(item.FileName) + + + @item.FileType + + @item.LastUpdateTime + @item.Length + + } + } + + + + + diff --git a/modules/virtual-file-explorer/src/Volo.Abp.VirtualFileExplorer.Web/Pages/VirtualFileExplorer/Index.cshtml.cs b/modules/virtual-file-explorer/src/Volo.Abp.VirtualFileExplorer.Web/Pages/VirtualFileExplorer/Index.cshtml.cs new file mode 100644 index 0000000000..01eb770761 --- /dev/null +++ b/modules/virtual-file-explorer/src/Volo.Abp.VirtualFileExplorer.Web/Pages/VirtualFileExplorer/Index.cshtml.cs @@ -0,0 +1,106 @@ +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.FileProviders; +using NUglify.Helpers; +using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Pagination; +using Volo.Abp.VirtualFileExplorer.Web.Models; +using Volo.Abp.VirtualFileSystem; +using Volo.Abp.VirtualFileSystem.Embedded; + +namespace Volo.Abp.VirtualFileExplorer.Web.Pages.VirtualFileExplorer +{ + public class IndexModel : VirtualFileExplorerPageModel + { + [BindProperty(SupportsGet = true)] + public string Path { get; set; } = "/"; + + [BindProperty(SupportsGet = true)] + public int CurrentPage { get; set; } = 1; + + [BindProperty(SupportsGet = true)] + public int PageSize { get; set; } = 10; + + public List FileInfoList { get; set; } + + public PagerModel PagerModel { get; set; } + + public string PathNavigation { get; set; } + + protected IVirtualFileProvider VirtualFileProvider { get; } + + + public IndexModel(IVirtualFileProvider virtualFileProvider) + { + VirtualFileProvider = virtualFileProvider; + } + + public virtual IActionResult OnGet() + { + var query = VirtualFileProvider.GetDirectoryContents(Path) + .Where(d => VirtualFileExplorerConsts.AllowFileInfoTypes.Contains(d.GetType().Name)) + .OrderByDescending(f => f.IsDirectory).ToList(); + + PagerModel = new PagerModel(query.Count, PageSize, CurrentPage, PageSize, $"/VirtualFileExplorer?Path={Path}&PageSize={PageSize}"); + + SetViewModel(query.Skip((CurrentPage - 1) * PageSize).Take(PageSize)); + SetPathNavigation(); + + return Page(); + } + + private void SetViewModel(IEnumerable fileInfos) + { + FileInfoList = new List(); + + foreach (var fileInfo in fileInfos) + { + var fileInfoViewModel = new FileInfoViewModel() + { + IsDirectory = fileInfo.IsDirectory, + Icon = "fas fa-file", + FileType = "file", + Length = fileInfo.Length+" bytes", + FileName = fileInfo.Name, + LastUpdateTime = fileInfo.LastModified.LocalDateTime + }; + + if (fileInfo.IsDirectory) + { + fileInfoViewModel.Icon = "fas fa-folder"; + fileInfoViewModel.FileType = "folder"; + fileInfoViewModel.Length = "/"; + fileInfoViewModel.FileName =$"{fileInfo.Name}"; + } + else + { + fileInfoViewModel.FilePath = ((EmbeddedResourceFileInfo) fileInfo).VirtualPath; + } + + FileInfoList.Add(fileInfoViewModel); + } + } + + private void SetPathNavigation() + { + var navigationBuild = new StringBuilder(); + var pathArray = Path.Split('/').Where(p => !p.IsNullOrWhiteSpace()); + var href = "/VirtualFileExplorer?path="; + + navigationBuild.Append($""); + + PathNavigation = navigationBuild.ToString(); + } + } +} diff --git a/modules/virtual-file-explorer/src/Volo.Abp.VirtualFileExplorer.Web/Pages/VirtualFileExplorer/VirtualFileExplorerPageModel.cs b/modules/virtual-file-explorer/src/Volo.Abp.VirtualFileExplorer.Web/Pages/VirtualFileExplorer/VirtualFileExplorerPageModel.cs new file mode 100644 index 0000000000..340861b946 --- /dev/null +++ b/modules/virtual-file-explorer/src/Volo.Abp.VirtualFileExplorer.Web/Pages/VirtualFileExplorer/VirtualFileExplorerPageModel.cs @@ -0,0 +1,16 @@ +using Volo.Abp.AspNetCore.Mvc.UI.RazorPages; +using Volo.Abp.VirtualFileExplorer.Localization; + +namespace Volo.Abp.VirtualFileExplorer.Web.Pages.VirtualFileExplorer +{ + /* Inherit your PageModel classes from this class. + */ + public abstract class VirtualFileExplorerPageModel : AbpPageModel + { + protected VirtualFileExplorerPageModel() + { + LocalizationResourceType = typeof(VirtualFileExplorerResource); + ObjectMapperContext = typeof(AbpVirtualFileExplorerWebModule); + } + } +} diff --git a/modules/virtual-file-explorer/src/Volo.Abp.VirtualFileExplorer.Web/Pages/VirtualFileExplorer/_ViewImports.cshtml b/modules/virtual-file-explorer/src/Volo.Abp.VirtualFileExplorer.Web/Pages/VirtualFileExplorer/_ViewImports.cshtml new file mode 100644 index 0000000000..c1da1f5f10 --- /dev/null +++ b/modules/virtual-file-explorer/src/Volo.Abp.VirtualFileExplorer.Web/Pages/VirtualFileExplorer/_ViewImports.cshtml @@ -0,0 +1,4 @@ +@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers +@addTagHelper *, Volo.Abp.AspNetCore.Mvc.UI +@addTagHelper *, Volo.Abp.AspNetCore.Mvc.UI.Bootstrap +@addTagHelper *, Volo.Abp.AspNetCore.Mvc.UI.Bundling \ No newline at end of file diff --git a/modules/virtual-file-explorer/src/Volo.Abp.VirtualFileExplorer.Web/Pages/VirtualFileExplorer/clipboard.js b/modules/virtual-file-explorer/src/Volo.Abp.VirtualFileExplorer.Web/Pages/VirtualFileExplorer/clipboard.js new file mode 100644 index 0000000000..14cb08654f --- /dev/null +++ b/modules/virtual-file-explorer/src/Volo.Abp.VirtualFileExplorer.Web/Pages/VirtualFileExplorer/clipboard.js @@ -0,0 +1,978 @@ +/*! + * clipboard.js v2.0.4 + * https://zenorocha.github.io/clipboard.js + * + * Licensed MIT © Zeno Rocha + */ +(function webpackUniversalModuleDefinition(root, factory) { + if(typeof exports === 'object' && typeof module === 'object') + module.exports = factory(); + else if(typeof define === 'function' && define.amd) + define([], factory); + else if(typeof exports === 'object') + exports["ClipboardJS"] = factory(); + else + root["ClipboardJS"] = factory(); +})(this, function() { +return /******/ (function(modules) { // webpackBootstrap +/******/ // The module cache +/******/ var installedModules = {}; +/******/ +/******/ // The require function +/******/ function __webpack_require__(moduleId) { +/******/ +/******/ // Check if module is in cache +/******/ if(installedModules[moduleId]) { +/******/ return installedModules[moduleId].exports; +/******/ } +/******/ // Create a new module (and put it into the cache) +/******/ var module = installedModules[moduleId] = { +/******/ i: moduleId, +/******/ l: false, +/******/ exports: {} +/******/ }; +/******/ +/******/ // Execute the module function +/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); +/******/ +/******/ // Flag the module as loaded +/******/ module.l = true; +/******/ +/******/ // Return the exports of the module +/******/ return module.exports; +/******/ } +/******/ +/******/ +/******/ // expose the modules object (__webpack_modules__) +/******/ __webpack_require__.m = modules; +/******/ +/******/ // expose the module cache +/******/ __webpack_require__.c = installedModules; +/******/ +/******/ // define getter function for harmony exports +/******/ __webpack_require__.d = function(exports, name, getter) { +/******/ if(!__webpack_require__.o(exports, name)) { +/******/ Object.defineProperty(exports, name, { enumerable: true, get: getter }); +/******/ } +/******/ }; +/******/ +/******/ // define __esModule on exports +/******/ __webpack_require__.r = function(exports) { +/******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) { +/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); +/******/ } +/******/ Object.defineProperty(exports, '__esModule', { value: true }); +/******/ }; +/******/ +/******/ // create a fake namespace object +/******/ // mode & 1: value is a module id, require it +/******/ // mode & 2: merge all properties of value into the ns +/******/ // mode & 4: return value when already ns object +/******/ // mode & 8|1: behave like require +/******/ __webpack_require__.t = function(value, mode) { +/******/ if(mode & 1) value = __webpack_require__(value); +/******/ if(mode & 8) return value; +/******/ if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value; +/******/ var ns = Object.create(null); +/******/ __webpack_require__.r(ns); +/******/ Object.defineProperty(ns, 'default', { enumerable: true, value: value }); +/******/ if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key)); +/******/ return ns; +/******/ }; +/******/ +/******/ // getDefaultExport function for compatibility with non-harmony modules +/******/ __webpack_require__.n = function(module) { +/******/ var getter = module && module.__esModule ? +/******/ function getDefault() { return module['default']; } : +/******/ function getModuleExports() { return module; }; +/******/ __webpack_require__.d(getter, 'a', getter); +/******/ return getter; +/******/ }; +/******/ +/******/ // Object.prototype.hasOwnProperty.call +/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; +/******/ +/******/ // __webpack_public_path__ +/******/ __webpack_require__.p = ""; +/******/ +/******/ +/******/ // Load entry module and return exports +/******/ return __webpack_require__(__webpack_require__.s = 0); +/******/ }) +/************************************************************************/ +/******/ ([ +/* 0 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; + +var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + +var _clipboardAction = __webpack_require__(1); + +var _clipboardAction2 = _interopRequireDefault(_clipboardAction); + +var _tinyEmitter = __webpack_require__(3); + +var _tinyEmitter2 = _interopRequireDefault(_tinyEmitter); + +var _goodListener = __webpack_require__(4); + +var _goodListener2 = _interopRequireDefault(_goodListener); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } + +function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } + +/** + * Base class which takes one or more elements, adds event listeners to them, + * and instantiates a new `ClipboardAction` on each click. + */ +var Clipboard = function (_Emitter) { + _inherits(Clipboard, _Emitter); + + /** + * @param {String|HTMLElement|HTMLCollection|NodeList} trigger + * @param {Object} options + */ + function Clipboard(trigger, options) { + _classCallCheck(this, Clipboard); + + var _this = _possibleConstructorReturn(this, (Clipboard.__proto__ || Object.getPrototypeOf(Clipboard)).call(this)); + + _this.resolveOptions(options); + _this.listenClick(trigger); + return _this; + } + + /** + * Defines if attributes would be resolved using internal setter functions + * or custom functions that were passed in the constructor. + * @param {Object} options + */ + + + _createClass(Clipboard, [{ + key: 'resolveOptions', + value: function resolveOptions() { + var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; + + this.action = typeof options.action === 'function' ? options.action : this.defaultAction; + this.target = typeof options.target === 'function' ? options.target : this.defaultTarget; + this.text = typeof options.text === 'function' ? options.text : this.defaultText; + this.container = _typeof(options.container) === 'object' ? options.container : document.body; + } + + /** + * Adds a click event listener to the passed trigger. + * @param {String|HTMLElement|HTMLCollection|NodeList} trigger + */ + + }, { + key: 'listenClick', + value: function listenClick(trigger) { + var _this2 = this; + + this.listener = (0, _goodListener2.default)(trigger, 'click', function (e) { + return _this2.onClick(e); + }); + } + + /** + * Defines a new `ClipboardAction` on each click event. + * @param {Event} e + */ + + }, { + key: 'onClick', + value: function onClick(e) { + var trigger = e.delegateTarget || e.currentTarget; + + if (this.clipboardAction) { + this.clipboardAction = null; + } + + this.clipboardAction = new _clipboardAction2.default({ + action: this.action(trigger), + target: this.target(trigger), + text: this.text(trigger), + container: this.container, + trigger: trigger, + emitter: this + }); + } + + /** + * Default `action` lookup function. + * @param {Element} trigger + */ + + }, { + key: 'defaultAction', + value: function defaultAction(trigger) { + return getAttributeValue('action', trigger); + } + + /** + * Default `target` lookup function. + * @param {Element} trigger + */ + + }, { + key: 'defaultTarget', + value: function defaultTarget(trigger) { + var selector = getAttributeValue('target', trigger); + + if (selector) { + return document.querySelector(selector); + } + } + + /** + * Returns the support of the given action, or all actions if no action is + * given. + * @param {String} [action] + */ + + }, { + key: 'defaultText', + + + /** + * Default `text` lookup function. + * @param {Element} trigger + */ + value: function defaultText(trigger) { + return getAttributeValue('text', trigger); + } + + /** + * Destroy lifecycle. + */ + + }, { + key: 'destroy', + value: function destroy() { + this.listener.destroy(); + + if (this.clipboardAction) { + this.clipboardAction.destroy(); + this.clipboardAction = null; + } + } + }], [{ + key: 'isSupported', + value: function isSupported() { + var action = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : ['copy', 'cut']; + + var actions = typeof action === 'string' ? [action] : action; + var support = !!document.queryCommandSupported; + + actions.forEach(function (action) { + support = support && !!document.queryCommandSupported(action); + }); + + return support; + } + }]); + + return Clipboard; +}(_tinyEmitter2.default); + +/** + * Helper function to retrieve attribute value. + * @param {String} suffix + * @param {Element} element + */ + + +function getAttributeValue(suffix, element) { + var attribute = 'data-clipboard-' + suffix; + + if (!element.hasAttribute(attribute)) { + return; + } + + return element.getAttribute(attribute); +} + +module.exports = Clipboard; + +/***/ }), +/* 1 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; + +var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + +var _select = __webpack_require__(2); + +var _select2 = _interopRequireDefault(_select); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +/** + * Inner class which performs selection from either `text` or `target` + * properties and then executes copy or cut operations. + */ +var ClipboardAction = function () { + /** + * @param {Object} options + */ + function ClipboardAction(options) { + _classCallCheck(this, ClipboardAction); + + this.resolveOptions(options); + this.initSelection(); + } + + /** + * Defines base properties passed from constructor. + * @param {Object} options + */ + + + _createClass(ClipboardAction, [{ + key: 'resolveOptions', + value: function resolveOptions() { + var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; + + this.action = options.action; + this.container = options.container; + this.emitter = options.emitter; + this.target = options.target; + this.text = options.text; + this.trigger = options.trigger; + + this.selectedText = ''; + } + + /** + * Decides which selection strategy is going to be applied based + * on the existence of `text` and `target` properties. + */ + + }, { + key: 'initSelection', + value: function initSelection() { + if (this.text) { + this.selectFake(); + } else if (this.target) { + this.selectTarget(); + } + } + + /** + * Creates a fake textarea element, sets its value from `text` property, + * and makes a selection on it. + */ + + }, { + key: 'selectFake', + value: function selectFake() { + var _this = this; + + var isRTL = document.documentElement.getAttribute('dir') == 'rtl'; + + this.removeFake(); + + this.fakeHandlerCallback = function () { + return _this.removeFake(); + }; + this.fakeHandler = this.container.addEventListener('click', this.fakeHandlerCallback) || true; + + this.fakeElem = document.createElement('textarea'); + // Prevent zooming on iOS + this.fakeElem.style.fontSize = '12pt'; + // Reset box model + this.fakeElem.style.border = '0'; + this.fakeElem.style.padding = '0'; + this.fakeElem.style.margin = '0'; + // Move element out of screen horizontally + this.fakeElem.style.position = 'absolute'; + this.fakeElem.style[isRTL ? 'right' : 'left'] = '-9999px'; + // Move element to the same position vertically + var yPosition = window.pageYOffset || document.documentElement.scrollTop; + this.fakeElem.style.top = yPosition + 'px'; + + this.fakeElem.setAttribute('readonly', ''); + this.fakeElem.value = this.text; + + this.container.appendChild(this.fakeElem); + + this.selectedText = (0, _select2.default)(this.fakeElem); + this.copyText(); + } + + /** + * Only removes the fake element after another click event, that way + * a user can hit `Ctrl+C` to copy because selection still exists. + */ + + }, { + key: 'removeFake', + value: function removeFake() { + if (this.fakeHandler) { + this.container.removeEventListener('click', this.fakeHandlerCallback); + this.fakeHandler = null; + this.fakeHandlerCallback = null; + } + + if (this.fakeElem) { + this.container.removeChild(this.fakeElem); + this.fakeElem = null; + } + } + + /** + * Selects the content from element passed on `target` property. + */ + + }, { + key: 'selectTarget', + value: function selectTarget() { + this.selectedText = (0, _select2.default)(this.target); + this.copyText(); + } + + /** + * Executes the copy operation based on the current selection. + */ + + }, { + key: 'copyText', + value: function copyText() { + var succeeded = void 0; + + try { + succeeded = document.execCommand(this.action); + } catch (err) { + succeeded = false; + } + + this.handleResult(succeeded); + } + + /** + * Fires an event based on the copy operation result. + * @param {Boolean} succeeded + */ + + }, { + key: 'handleResult', + value: function handleResult(succeeded) { + this.emitter.emit(succeeded ? 'success' : 'error', { + action: this.action, + text: this.selectedText, + trigger: this.trigger, + clearSelection: this.clearSelection.bind(this) + }); + } + + /** + * Moves focus away from `target` and back to the trigger, removes current selection. + */ + + }, { + key: 'clearSelection', + value: function clearSelection() { + if (this.trigger) { + this.trigger.focus(); + } + + window.getSelection().removeAllRanges(); + } + + /** + * Sets the `action` to be performed which can be either 'copy' or 'cut'. + * @param {String} action + */ + + }, { + key: 'destroy', + + + /** + * Destroy lifecycle. + */ + value: function destroy() { + this.removeFake(); + } + }, { + key: 'action', + set: function set() { + var action = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'copy'; + + this._action = action; + + if (this._action !== 'copy' && this._action !== 'cut') { + throw new Error('Invalid "action" value, use either "copy" or "cut"'); + } + } + + /** + * Gets the `action` property. + * @return {String} + */ + , + get: function get() { + return this._action; + } + + /** + * Sets the `target` property using an element + * that will be have its content copied. + * @param {Element} target + */ + + }, { + key: 'target', + set: function set(target) { + if (target !== undefined) { + if (target && (typeof target === 'undefined' ? 'undefined' : _typeof(target)) === 'object' && target.nodeType === 1) { + if (this.action === 'copy' && target.hasAttribute('disabled')) { + throw new Error('Invalid "target" attribute. Please use "readonly" instead of "disabled" attribute'); + } + + if (this.action === 'cut' && (target.hasAttribute('readonly') || target.hasAttribute('disabled'))) { + throw new Error('Invalid "target" attribute. You can\'t cut text from elements with "readonly" or "disabled" attributes'); + } + + this._target = target; + } else { + throw new Error('Invalid "target" value, use a valid Element'); + } + } + } + + /** + * Gets the `target` property. + * @return {String|HTMLElement} + */ + , + get: function get() { + return this._target; + } + }]); + + return ClipboardAction; +}(); + +module.exports = ClipboardAction; + +/***/ }), +/* 2 */ +/***/ (function(module, exports) { + +function select(element) { + var selectedText; + + if (element.nodeName === 'SELECT') { + element.focus(); + + selectedText = element.value; + } + else if (element.nodeName === 'INPUT' || element.nodeName === 'TEXTAREA') { + var isReadOnly = element.hasAttribute('readonly'); + + if (!isReadOnly) { + element.setAttribute('readonly', ''); + } + + element.select(); + element.setSelectionRange(0, element.value.length); + + if (!isReadOnly) { + element.removeAttribute('readonly'); + } + + selectedText = element.value; + } + else { + if (element.hasAttribute('contenteditable')) { + element.focus(); + } + + var selection = window.getSelection(); + var range = document.createRange(); + + range.selectNodeContents(element); + selection.removeAllRanges(); + selection.addRange(range); + + selectedText = selection.toString(); + } + + return selectedText; +} + +module.exports = select; + + +/***/ }), +/* 3 */ +/***/ (function(module, exports) { + +function E () { + // Keep this empty so it's easier to inherit from + // (via https://github.com/lipsmack from https://github.com/scottcorgan/tiny-emitter/issues/3) +} + +E.prototype = { + on: function (name, callback, ctx) { + var e = this.e || (this.e = {}); + + (e[name] || (e[name] = [])).push({ + fn: callback, + ctx: ctx + }); + + return this; + }, + + once: function (name, callback, ctx) { + var self = this; + function listener () { + self.off(name, listener); + callback.apply(ctx, arguments); + }; + + listener._ = callback + return this.on(name, listener, ctx); + }, + + emit: function (name) { + var data = [].slice.call(arguments, 1); + var evtArr = ((this.e || (this.e = {}))[name] || []).slice(); + var i = 0; + var len = evtArr.length; + + for (i; i < len; i++) { + evtArr[i].fn.apply(evtArr[i].ctx, data); + } + + return this; + }, + + off: function (name, callback) { + var e = this.e || (this.e = {}); + var evts = e[name]; + var liveEvents = []; + + if (evts && callback) { + for (var i = 0, len = evts.length; i < len; i++) { + if (evts[i].fn !== callback && evts[i].fn._ !== callback) + liveEvents.push(evts[i]); + } + } + + // Remove event from queue to prevent memory leak + // Suggested by https://github.com/lazd + // Ref: https://github.com/scottcorgan/tiny-emitter/commit/c6ebfaa9bc973b33d110a84a307742b7cf94c953#commitcomment-5024910 + + (liveEvents.length) + ? e[name] = liveEvents + : delete e[name]; + + return this; + } +}; + +module.exports = E; + + +/***/ }), +/* 4 */ +/***/ (function(module, exports, __webpack_require__) { + +var is = __webpack_require__(5); +var delegate = __webpack_require__(6); + +/** + * Validates all params and calls the right + * listener function based on its target type. + * + * @param {String|HTMLElement|HTMLCollection|NodeList} target + * @param {String} type + * @param {Function} callback + * @return {Object} + */ +function listen(target, type, callback) { + if (!target && !type && !callback) { + throw new Error('Missing required arguments'); + } + + if (!is.string(type)) { + throw new TypeError('Second argument must be a String'); + } + + if (!is.fn(callback)) { + throw new TypeError('Third argument must be a Function'); + } + + if (is.node(target)) { + return listenNode(target, type, callback); + } + else if (is.nodeList(target)) { + return listenNodeList(target, type, callback); + } + else if (is.string(target)) { + return listenSelector(target, type, callback); + } + else { + throw new TypeError('First argument must be a String, HTMLElement, HTMLCollection, or NodeList'); + } +} + +/** + * Adds an event listener to a HTML element + * and returns a remove listener function. + * + * @param {HTMLElement} node + * @param {String} type + * @param {Function} callback + * @return {Object} + */ +function listenNode(node, type, callback) { + node.addEventListener(type, callback); + + return { + destroy: function() { + node.removeEventListener(type, callback); + } + } +} + +/** + * Add an event listener to a list of HTML elements + * and returns a remove listener function. + * + * @param {NodeList|HTMLCollection} nodeList + * @param {String} type + * @param {Function} callback + * @return {Object} + */ +function listenNodeList(nodeList, type, callback) { + Array.prototype.forEach.call(nodeList, function(node) { + node.addEventListener(type, callback); + }); + + return { + destroy: function() { + Array.prototype.forEach.call(nodeList, function(node) { + node.removeEventListener(type, callback); + }); + } + } +} + +/** + * Add an event listener to a selector + * and returns a remove listener function. + * + * @param {String} selector + * @param {String} type + * @param {Function} callback + * @return {Object} + */ +function listenSelector(selector, type, callback) { + return delegate(document.body, selector, type, callback); +} + +module.exports = listen; + + +/***/ }), +/* 5 */ +/***/ (function(module, exports) { + +/** + * Check if argument is a HTML element. + * + * @param {Object} value + * @return {Boolean} + */ +exports.node = function(value) { + return value !== undefined + && value instanceof HTMLElement + && value.nodeType === 1; +}; + +/** + * Check if argument is a list of HTML elements. + * + * @param {Object} value + * @return {Boolean} + */ +exports.nodeList = function(value) { + var type = Object.prototype.toString.call(value); + + return value !== undefined + && (type === '[object NodeList]' || type === '[object HTMLCollection]') + && ('length' in value) + && (value.length === 0 || exports.node(value[0])); +}; + +/** + * Check if argument is a string. + * + * @param {Object} value + * @return {Boolean} + */ +exports.string = function(value) { + return typeof value === 'string' + || value instanceof String; +}; + +/** + * Check if argument is a function. + * + * @param {Object} value + * @return {Boolean} + */ +exports.fn = function(value) { + var type = Object.prototype.toString.call(value); + + return type === '[object Function]'; +}; + + +/***/ }), +/* 6 */ +/***/ (function(module, exports, __webpack_require__) { + +var closest = __webpack_require__(7); + +/** + * Delegates event to a selector. + * + * @param {Element} element + * @param {String} selector + * @param {String} type + * @param {Function} callback + * @param {Boolean} useCapture + * @return {Object} + */ +function _delegate(element, selector, type, callback, useCapture) { + var listenerFn = listener.apply(this, arguments); + + element.addEventListener(type, listenerFn, useCapture); + + return { + destroy: function() { + element.removeEventListener(type, listenerFn, useCapture); + } + } +} + +/** + * Delegates event to a selector. + * + * @param {Element|String|Array} [elements] + * @param {String} selector + * @param {String} type + * @param {Function} callback + * @param {Boolean} useCapture + * @return {Object} + */ +function delegate(elements, selector, type, callback, useCapture) { + // Handle the regular Element usage + if (typeof elements.addEventListener === 'function') { + return _delegate.apply(null, arguments); + } + + // Handle Element-less usage, it defaults to global delegation + if (typeof type === 'function') { + // Use `document` as the first parameter, then apply arguments + // This is a short way to .unshift `arguments` without running into deoptimizations + return _delegate.bind(null, document).apply(null, arguments); + } + + // Handle Selector-based usage + if (typeof elements === 'string') { + elements = document.querySelectorAll(elements); + } + + // Handle Array-like based usage + return Array.prototype.map.call(elements, function (element) { + return _delegate(element, selector, type, callback, useCapture); + }); +} + +/** + * Finds closest match and invokes callback. + * + * @param {Element} element + * @param {String} selector + * @param {String} type + * @param {Function} callback + * @return {Function} + */ +function listener(element, selector, type, callback) { + return function(e) { + e.delegateTarget = closest(e.target, selector); + + if (e.delegateTarget) { + callback.call(element, e); + } + } +} + +module.exports = delegate; + + +/***/ }), +/* 7 */ +/***/ (function(module, exports) { + +var DOCUMENT_NODE_TYPE = 9; + +/** + * A polyfill for Element.matches() + */ +if (typeof Element !== 'undefined' && !Element.prototype.matches) { + var proto = Element.prototype; + + proto.matches = proto.matchesSelector || + proto.mozMatchesSelector || + proto.msMatchesSelector || + proto.oMatchesSelector || + proto.webkitMatchesSelector; +} + +/** + * Finds the closest parent that matches a selector. + * + * @param {Element} element + * @param {String} selector + * @return {Function} + */ +function closest (element, selector) { + while (element && element.nodeType !== DOCUMENT_NODE_TYPE) { + if (typeof element.matches === 'function' && + element.matches(selector)) { + return element; + } + element = element.parentNode; + } +} + +module.exports = closest; + + +/***/ }) +/******/ ]); +}); \ No newline at end of file diff --git a/modules/virtual-file-explorer/src/Volo.Abp.VirtualFileExplorer.Web/Pages/VirtualFileExplorer/index.css b/modules/virtual-file-explorer/src/Volo.Abp.VirtualFileExplorer.Web/Pages/VirtualFileExplorer/index.css new file mode 100644 index 0000000000..95d078718f --- /dev/null +++ b/modules/virtual-file-explorer/src/Volo.Abp.VirtualFileExplorer.Web/Pages/VirtualFileExplorer/index.css @@ -0,0 +1,3 @@ +.breadcrumb { + background-color: transparent; +} diff --git a/modules/virtual-file-explorer/src/Volo.Abp.VirtualFileExplorer.Web/Pages/VirtualFileExplorer/index.js b/modules/virtual-file-explorer/src/Volo.Abp.VirtualFileExplorer.Web/Pages/VirtualFileExplorer/index.js new file mode 100644 index 0000000000..a67939ca43 --- /dev/null +++ b/modules/virtual-file-explorer/src/Volo.Abp.VirtualFileExplorer.Web/Pages/VirtualFileExplorer/index.js @@ -0,0 +1,7 @@ +var _fileContentModal = new abp.ModalManager(abp.appPath + 'VirtualFileExplorer/FileContentModal'); + +function showContent(filePath) { + _fileContentModal.open({ + filePath: filePath + }); +} diff --git a/modules/virtual-file-explorer/src/Volo.Abp.VirtualFileExplorer.Web/Pages/VirtualFileExplorer/prism.css b/modules/virtual-file-explorer/src/Volo.Abp.VirtualFileExplorer.Web/Pages/VirtualFileExplorer/prism.css new file mode 100644 index 0000000000..c8de1ce04e --- /dev/null +++ b/modules/virtual-file-explorer/src/Volo.Abp.VirtualFileExplorer.Web/Pages/VirtualFileExplorer/prism.css @@ -0,0 +1,195 @@ +/* PrismJS 1.20.0 +/** + * prism.js Dark theme for JavaScript, CSS and HTML + * Based on the slides of the talk “/Reg(exp){2}lained/” + * @author Lea Verou + */ + +code[class*="language-"], +pre[class*="language-"] { + color: white; + background: none; + text-shadow: 0 -.1em .2em black; + font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace; + font-size: 1em; + text-align: left; + white-space: pre; + word-spacing: normal; + word-break: normal; + word-wrap: normal; + line-height: 1.5; + + -moz-tab-size: 4; + -o-tab-size: 4; + tab-size: 4; + + -webkit-hyphens: none; + -moz-hyphens: none; + -ms-hyphens: none; + hyphens: none; +} + +@media print { + code[class*="language-"], + pre[class*="language-"] { + text-shadow: none; + } +} + +pre[class*="language-"], +:not(pre) > code[class*="language-"] { + background: hsl(0, 0%, 8%); +} + +/* Code blocks */ +pre[class*="language-"] { + padding: 1em; + margin: .5em 0; + overflow: auto; + border: .3em solid hsl(0, 0%, 8%); + border-radius: .5em; + box-shadow: 1px 1px .5em black inset; +} + +/* Inline code */ +:not(pre) > code[class*="language-"] { + padding: .15em .2em .05em; + border-radius: .3em; + border: .13em solid hsl(0, 0%, 8%); + box-shadow: 1px 1px .3em -.1em black inset; + white-space: normal; +} + +.token.comment, +.token.prolog, +.token.doctype, +.token.cdata { + color: hsl(30, 20%, 50%); +} + +.token.punctuation { + opacity: .7; +} + +.token.namespace { + opacity: .7; +} + +.token.property, +.token.tag, +.token.boolean, +.token.number, +.token.constant, +.token.symbol { + color: hsl(350, 40%, 70%); +} + +.token.selector, +.token.attr-name, +.token.string, +.token.char, +.token.builtin, +.token.inserted { + color: hsl(75, 70%, 60%); +} + +.token.operator, +.token.entity, +.token.url, +.language-css .token.string, +.style .token.string, +.token.variable { + color: hsl(40, 90%, 60%); +} + +.token.atrule, +.token.attr-value, +.token.keyword { + color: hsl(350, 40%, 70%); +} + +.token.regex, +.token.important { + color: #e90; +} + +.token.important, +.token.bold { + font-weight: bold; +} +.token.italic { + font-style: italic; +} + +.token.entity { + cursor: help; +} + +.token.deleted { + color: red; +} + +div.code-toolbar { + position: relative; +} + +div.code-toolbar > .toolbar { + position: absolute; + top: .3em; + right: .2em; + transition: opacity 0.3s ease-in-out; + opacity: 0; +} + +div.code-toolbar:hover > .toolbar { + opacity: 1; +} + +/* Separate line b/c rules are thrown out if selector is invalid. + IE11 and old Edge versions don't support :focus-within. */ +div.code-toolbar:focus-within > .toolbar { + opacity: 1; +} + +div.code-toolbar > .toolbar .toolbar-item { + display: inline-block; +} + +div.code-toolbar > .toolbar a { + cursor: pointer; +} + +div.code-toolbar > .toolbar button { + background: none; + border: 0; + color: inherit; + font: inherit; + line-height: normal; + overflow: visible; + padding: 0; + -webkit-user-select: none; /* for button */ + -moz-user-select: none; + -ms-user-select: none; +} + +div.code-toolbar > .toolbar a, +div.code-toolbar > .toolbar button, +div.code-toolbar > .toolbar span { + color: #bbb; + font-size: .8em; + padding: 0 .5em; + background: #f5f2f0; + background: rgba(224, 224, 224, 0.2); + box-shadow: 0 2px 0 0 rgba(0,0,0,0.2); + border-radius: .5em; +} + +div.code-toolbar > .toolbar a:hover, +div.code-toolbar > .toolbar a:focus, +div.code-toolbar > .toolbar button:hover, +div.code-toolbar > .toolbar button:focus, +div.code-toolbar > .toolbar span:hover, +div.code-toolbar > .toolbar span:focus { + color: inherit; + text-decoration: none; +} diff --git a/modules/virtual-file-explorer/src/Volo.Abp.VirtualFileExplorer.Web/Pages/VirtualFileExplorer/prism.js b/modules/virtual-file-explorer/src/Volo.Abp.VirtualFileExplorer.Web/Pages/VirtualFileExplorer/prism.js new file mode 100644 index 0000000000..36d04d6910 --- /dev/null +++ b/modules/virtual-file-explorer/src/Volo.Abp.VirtualFileExplorer.Web/Pages/VirtualFileExplorer/prism.js @@ -0,0 +1,1275 @@ +/* PrismJS 1.20.0 +https://prismjs.com/download.html#themes=prism-dark&languages=markup+css+clike+javascript&plugins=toolbar+copy-to-clipboard */ +var _self = (typeof window !== 'undefined') + ? window // if in browser + : ( + (typeof WorkerGlobalScope !== 'undefined' && self instanceof WorkerGlobalScope) + ? self // if in worker + : {} // if in node js + ); + +/** + * Prism: Lightweight, robust, elegant syntax highlighting + * MIT license http://www.opensource.org/licenses/mit-license.php/ + * @author Lea Verou http://lea.verou.me + */ + +var Prism = (function (_self){ + +// Private helper vars + var lang = /\blang(?:uage)?-([\w-]+)\b/i; + var uniqueId = 0; + + + var _ = { + manual: _self.Prism && _self.Prism.manual, + disableWorkerMessageHandler: _self.Prism && _self.Prism.disableWorkerMessageHandler, + util: { + encode: function encode(tokens) { + if (tokens instanceof Token) { + return new Token(tokens.type, encode(tokens.content), tokens.alias); + } else if (Array.isArray(tokens)) { + return tokens.map(encode); + } else { + return tokens.replace(/&/g, '&').replace(/' + env.content + ''; + }; + + /** + * @param {string} text + * @param {LinkedList} tokenList + * @param {any} grammar + * @param {LinkedListNode} startNode + * @param {number} startPos + * @param {boolean} [oneshot=false] + * @param {string} [target] + */ + function matchGrammar(text, tokenList, grammar, startNode, startPos, oneshot, target) { + for (var token in grammar) { + if (!grammar.hasOwnProperty(token) || !grammar[token]) { + continue; + } + + var patterns = grammar[token]; + patterns = Array.isArray(patterns) ? patterns : [patterns]; + + for (var j = 0; j < patterns.length; ++j) { + if (target && target == token + ',' + j) { + return; + } + + var pattern = patterns[j], + inside = pattern.inside, + lookbehind = !!pattern.lookbehind, + greedy = !!pattern.greedy, + lookbehindLength = 0, + alias = pattern.alias; + + if (greedy && !pattern.pattern.global) { + // Without the global flag, lastIndex won't work + var flags = pattern.pattern.toString().match(/[imsuy]*$/)[0]; + pattern.pattern = RegExp(pattern.pattern.source, flags + 'g'); + } + + pattern = pattern.pattern || pattern; + + for ( // iterate the token list and keep track of the current token/string position + var currentNode = startNode.next, pos = startPos; + currentNode !== tokenList.tail; + pos += currentNode.value.length, currentNode = currentNode.next + ) { + + var str = currentNode.value; + + if (tokenList.length > text.length) { + // Something went terribly wrong, ABORT, ABORT! + return; + } + + if (str instanceof Token) { + continue; + } + + var removeCount = 1; // this is the to parameter of removeBetween + + if (greedy && currentNode != tokenList.tail.prev) { + pattern.lastIndex = pos; + var match = pattern.exec(text); + if (!match) { + break; + } + + var from = match.index + (lookbehind && match[1] ? match[1].length : 0); + var to = match.index + match[0].length; + var p = pos; + + // find the node that contains the match + p += currentNode.value.length; + while (from >= p) { + currentNode = currentNode.next; + p += currentNode.value.length; + } + // adjust pos (and p) + p -= currentNode.value.length; + pos = p; + + // the current node is a Token, then the match starts inside another Token, which is invalid + if (currentNode.value instanceof Token) { + continue; + } + + // find the last node which is affected by this match + for ( + var k = currentNode; + k !== tokenList.tail && (p < to || (typeof k.value === 'string' && !k.prev.value.greedy)); + k = k.next + ) { + removeCount++; + p += k.value.length; + } + removeCount--; + + // replace with the new match + str = text.slice(pos, p); + match.index -= pos; + } else { + pattern.lastIndex = 0; + + var match = pattern.exec(str); + } + + if (!match) { + if (oneshot) { + break; + } + + continue; + } + + if (lookbehind) { + lookbehindLength = match[1] ? match[1].length : 0; + } + + var from = match.index + lookbehindLength, + match = match[0].slice(lookbehindLength), + to = from + match.length, + before = str.slice(0, from), + after = str.slice(to); + + var removeFrom = currentNode.prev; + + if (before) { + removeFrom = addAfter(tokenList, removeFrom, before); + pos += before.length; + } + + removeRange(tokenList, removeFrom, removeCount); + + var wrapped = new Token(token, inside ? _.tokenize(match, inside) : match, alias, match, greedy); + currentNode = addAfter(tokenList, removeFrom, wrapped); + + if (after) { + addAfter(tokenList, currentNode, after); + } + + + if (removeCount > 1) + matchGrammar(text, tokenList, grammar, currentNode.prev, pos, true, token + ',' + j); + + if (oneshot) + break; + } + } + } + } + + /** + * @typedef LinkedListNode + * @property {T} value + * @property {LinkedListNode | null} prev The previous node. + * @property {LinkedListNode | null} next The next node. + * @template T + */ + + /** + * @template T + */ + function LinkedList() { + /** @type {LinkedListNode} */ + var head = { value: null, prev: null, next: null }; + /** @type {LinkedListNode} */ + var tail = { value: null, prev: head, next: null }; + head.next = tail; + + /** @type {LinkedListNode} */ + this.head = head; + /** @type {LinkedListNode} */ + this.tail = tail; + this.length = 0; + } + + /** + * Adds a new node with the given value to the list. + * @param {LinkedList} list + * @param {LinkedListNode} node + * @param {T} value + * @returns {LinkedListNode} The added node. + * @template T + */ + function addAfter(list, node, value) { + // assumes that node != list.tail && values.length >= 0 + var next = node.next; + + var newNode = { value: value, prev: node, next: next }; + node.next = newNode; + next.prev = newNode; + list.length++; + + return newNode; + } + /** + * Removes `count` nodes after the given node. The given node will not be removed. + * @param {LinkedList} list + * @param {LinkedListNode} node + * @param {number} count + * @template T + */ + function removeRange(list, node, count) { + var next = node.next; + for (var i = 0; i < count && next !== list.tail; i++) { + next = next.next; + } + node.next = next; + next.prev = node; + list.length -= i; + } + /** + * @param {LinkedList} list + * @returns {T[]} + * @template T + */ + function toArray(list) { + var array = []; + var node = list.head.next; + while (node !== list.tail) { + array.push(node.value); + node = node.next; + } + return array; + } + + + if (!_self.document) { + if (!_self.addEventListener) { + // in Node.js + return _; + } + + if (!_.disableWorkerMessageHandler) { + // In worker + _self.addEventListener('message', function (evt) { + var message = JSON.parse(evt.data), + lang = message.language, + code = message.code, + immediateClose = message.immediateClose; + + _self.postMessage(_.highlight(code, _.languages[lang], lang)); + if (immediateClose) { + _self.close(); + } + }, false); + } + + return _; + } + +//Get current script and highlight + var script = _.util.currentScript(); + + if (script) { + _.filename = script.src; + + if (script.hasAttribute('data-manual')) { + _.manual = true; + } + } + + function highlightAutomaticallyCallback() { + if (!_.manual) { + _.highlightAll(); + } + } + + if (!_.manual) { + // If the document state is "loading", then we'll use DOMContentLoaded. + // If the document state is "interactive" and the prism.js script is deferred, then we'll also use the + // DOMContentLoaded event because there might be some plugins or languages which have also been deferred and they + // might take longer one animation frame to execute which can create a race condition where only some plugins have + // been loaded when Prism.highlightAll() is executed, depending on how fast resources are loaded. + // See https://github.com/PrismJS/prism/issues/2102 + var readyState = document.readyState; + if (readyState === 'loading' || readyState === 'interactive' && script && script.defer) { + document.addEventListener('DOMContentLoaded', highlightAutomaticallyCallback); + } else { + if (window.requestAnimationFrame) { + window.requestAnimationFrame(highlightAutomaticallyCallback); + } else { + window.setTimeout(highlightAutomaticallyCallback, 16); + } + } + } + + return _; + +})(_self); + +if (typeof module !== 'undefined' && module.exports) { + module.exports = Prism; +} + +// hack for components to work correctly in node.js +if (typeof global !== 'undefined') { + global.Prism = Prism; +} +; +Prism.languages.markup = { + 'comment': //, + 'prolog': /<\?[\s\S]+?\?>/, + 'doctype': { + pattern: /"'[\]]|"[^"]*"|'[^']*')+(?:\[(?:[^<"'\]]|"[^"]*"|'[^']*'|<(?!!--)|)*\]\s*)?>/i, + greedy: true + }, + 'cdata': //i, + 'tag': { + pattern: /<\/?(?!\d)[^\s>\/=$<%]+(?:\s(?:\s*[^\s>\/=]+(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+(?=[\s>]))|(?=[\s/>])))+)?\s*\/?>/, + greedy: true, + inside: { + 'tag': { + pattern: /^<\/?[^\s>\/]+/, + inside: { + 'punctuation': /^<\/?/, + 'namespace': /^[^\s>\/:]+:/ + } + }, + 'attr-value': { + pattern: /=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+)/, + inside: { + 'punctuation': [ + /^=/, + { + pattern: /^(\s*)["']|["']$/, + lookbehind: true + } + ] + } + }, + 'punctuation': /\/?>/, + 'attr-name': { + pattern: /[^\s>\/]+/, + inside: { + 'namespace': /^[^\s>\/:]+:/ + } + } + + } + }, + 'entity': /&#?[\da-z]{1,8};/i +}; + +Prism.languages.markup['tag'].inside['attr-value'].inside['entity'] = + Prism.languages.markup['entity']; + +// Plugin to make entity title show the real entity, idea by Roman Komarov +Prism.hooks.add('wrap', function(env) { + + if (env.type === 'entity') { + env.attributes['title'] = env.content.replace(/&/, '&'); + } +}); + +Object.defineProperty(Prism.languages.markup.tag, 'addInlined', { + /** + * Adds an inlined language to markup. + * + * An example of an inlined language is CSS with `